Compare commits
14 Commits
jwesson/in
...
open-relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b93940a37 | ||
|
|
e0cdcbaa6c | ||
|
|
8488e5840f | ||
|
|
09ac1a7ce0 | ||
|
|
fc9e395a94 | ||
|
|
d963c99a6d | ||
|
|
4165066830 | ||
|
|
e427d50336 | ||
|
|
d8bac925ab | ||
|
|
92793495d7 | ||
|
|
1dfbe648cb | ||
|
|
e2c3cf5517 | ||
|
|
d22a1652fc | ||
|
|
7e009a76d8 |
4
.env
4
.env
@@ -22,8 +22,8 @@ LOGO_URL=''
|
|||||||
LOGO_TRADEMARK_URL=''
|
LOGO_TRADEMARK_URL=''
|
||||||
LOGO_WHITE_URL=''
|
LOGO_WHITE_URL=''
|
||||||
FAVICON_URL=''
|
FAVICON_URL=''
|
||||||
|
ENABLE_LEARNER_RECORD_MFE=''
|
||||||
|
LEARNER_RECORD_MFE_BASE_URL=''
|
||||||
COLLECT_YEAR_OF_BIRTH=true
|
COLLECT_YEAR_OF_BIRTH=true
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
SEARCH_CATALOG_URL=''
|
|
||||||
ENABLE_SKILLS_BUILDER_PROFILE=''
|
|
||||||
|
|||||||
@@ -23,8 +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_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||||
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
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
|
COLLECT_YEAR_OF_BIRTH=true
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
MFE_CONFIG_API_URL=''
|
MFE_CONFIG_API_URL=''
|
||||||
SEARCH_CATALOG_URL='http://localhost:18000/courses'
|
|
||||||
ENABLE_SKILLS_BUILDER_PROFILE=''
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
|||||||
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
ENABLE_LEARNER_RECORD_MFE=''
|
ENABLE_LEARNER_RECORD_MFE=''
|
||||||
ENABLE_SKILLS_BUILDER_PROFILE=''
|
|
||||||
LEARNER_RECORD_MFE_BASE_URL='http://localhost:1990'
|
LEARNER_RECORD_MFE_BASE_URL='http://localhost:1990'
|
||||||
COLLECT_YEAR_OF_BIRTH=true
|
COLLECT_YEAR_OF_BIRTH=true
|
||||||
APP_ID=''
|
APP_ID=''
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ jobs:
|
|||||||
secrets:
|
secrets:
|
||||||
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||||
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
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 }}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
|||||||
- i18n_extract
|
- i18n_extract
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
|
node: [16]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Setup Nodejs Env
|
|
||||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VER }}
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm install -g npm@8.x.x
|
||||||
- run: make requirements
|
- run: make requirements
|
||||||
- run: make test NPM_TESTS=build
|
- run: make test NPM_TESTS=build
|
||||||
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
||||||
|
|||||||
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version-check:
|
version-check:
|
||||||
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
|
||||||
|
|||||||
12
.github/workflows/self-assign-issue.yml
vendored
12
.github/workflows/self-assign-issue.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
# 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
|
|
||||||
12
.github/workflows/update-browserslist-db.yml
vendored
12
.github/workflows/update-browserslist-db.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
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 }}
|
|
||||||
25
Makefile
Normal file → Executable file
25
Makefile
Normal file → Executable file
@@ -1,13 +1,15 @@
|
|||||||
export TRANSIFEX_RESOURCE = frontend-app-profile
|
export TRANSIFEX_RESOURCE = frontend-app-profile
|
||||||
transifex_resource = frontend-app-profile
|
transifex_resource = frontend-app-profile
|
||||||
transifex_langs = "ar,de,de_DE,es_419,fa_IR,fr,fr_CA,hi,it,it_IT,pt,pt_PT,ru,uk,zh_CN"
|
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
|
||||||
|
|
||||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
|
||||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
i18n = ./src/i18n
|
i18n = ./src/i18n
|
||||||
transifex_input = $(i18n)/transifex_input.json
|
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 .
|
# This directory must match .babelrc .
|
||||||
transifex_temp = ./temp/babel-plugin-formatjs
|
transifex_temp = ./temp/babel-plugin-react-intl
|
||||||
|
|
||||||
NPM_TESTS=build i18n_extract lint test
|
NPM_TESTS=build i18n_extract lint test
|
||||||
|
|
||||||
@@ -50,24 +52,9 @@ push_translations:
|
|||||||
# Pushing comments to Transifex...
|
# Pushing comments to Transifex...
|
||||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||||
|
|
||||||
ifeq ($(OPENEDX_ATLAS_PULL),)
|
|
||||||
# Pulls translations from Transifex.
|
# Pulls translations from Transifex.
|
||||||
pull_translations:
|
pull_translations:
|
||||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
tx pull -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.
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
152
README.rst
152
README.rst
@@ -1,147 +1,57 @@
|
|||||||
#####################
|
|Build Status| |Codecov| |license|
|
||||||
|
|
||||||
frontend-app-profile
|
frontend-app-profile
|
||||||
#####################
|
====================
|
||||||
|
|
||||||
|license-badge| |status-badge| |ci-badge| |codecov-badge|
|
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| 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 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.
|
When a user views someone else's profile, they see all those fields that that user set as public.
|
||||||
|
|
||||||
***************
|
----------
|
||||||
Getting Started
|
|
||||||
***************
|
|
||||||
|
|
||||||
Installation
|
Development
|
||||||
============
|
-----------
|
||||||
|
|
||||||
Follow these steps to provision, run, and enable an instance of the
|
Start Devstack
|
||||||
Profile MFE for local development via the `devstack`_.
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.. _devstack: https://github.com/openedx/devstack#getting-started
|
To use this application `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
|
||||||
|
|
||||||
#. To use this application, `devstack <https://github.com/openedx/devstack>`__ must be running and you must be logged into it.
|
- Start devstack
|
||||||
|
- Log in (http://localhost:18000/login)
|
||||||
|
|
||||||
* Start devstack
|
Start the development server
|
||||||
* Log in (http://localhost:18000/login)
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
#. To run Profile, install requirements and start the development server by running:
|
In this project, install requirements and start the development server by running:
|
||||||
|
|
||||||
.. code-block::
|
.. code:: bash
|
||||||
|
|
||||||
1. Clone your new repo:
|
npm install
|
||||||
|
npm start # The server will run on port 1995
|
||||||
|
|
||||||
``git clone https://github.com/openedx/frontend-app-profile.git``
|
Once the dev server is up visit http://localhost:1995/u/staff.
|
||||||
|
|
||||||
2. Use node v18.x.
|
----------
|
||||||
|
|
||||||
The current version of the micro-frontend build scripts support node 18.
|
Configuration and Deployment
|
||||||
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:
|
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-block::
|
.. code:: bash
|
||||||
|
|
||||||
NODE_ENV=production ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
NODE_ENV=production ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
||||||
|
|
||||||
Getting Help
|
|
||||||
============
|
|
||||||
|
|
||||||
If you're having trouble, we have discussion forums at
|
For more information see the document: `Micro-frontend applications in Open
|
||||||
https://discuss.openedx.org where you can connect with others in the community.
|
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/micro-frontends-in-open-edx.html>`__.
|
||||||
|
|
||||||
Our real-time conversations are on Slack. You can request a `Slack
|
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-profile.svg?branch=master
|
||||||
invitation`_, then join our `community Slack workspace`_. Because this is a
|
:target: https://travis-ci.org/edx/frontend-app-profile
|
||||||
frontend repository, the best place to discuss it would be in the `#wg-frontend
|
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-profile
|
||||||
channel`_.
|
:target: https://codecov.io/gh/edx/frontend-app-profile
|
||||||
|
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-profile.svg
|
||||||
For anything non-trivial, the best path is to open an issue in this repository
|
:target: @edx/frontend-app-profile
|
||||||
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.
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# 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.
|
|
||||||
32341
package-lock.json
generated
32341
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
56
package.json
56
package.json
@@ -10,12 +10,11 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"build": "fedx-scripts webpack",
|
||||||
"i18n_extract": "fedx-scripts formatjs extract",
|
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
||||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"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": {
|
"bugs": {
|
||||||
"url": "https://github.com/openedx/frontend-app-profile/issues"
|
"url": "https://github.com/openedx/frontend-app-profile/issues"
|
||||||
@@ -28,54 +27,49 @@
|
|||||||
"extends @edx/browserslist-config"
|
"extends @edx/browserslist-config"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||||
"@edx/frontend-component-footer": "12.5.1",
|
"@edx/frontend-component-footer": "12.0.0",
|
||||||
"@edx/frontend-component-header": "4.8.0",
|
"@edx/frontend-component-header": "4.0.0",
|
||||||
"@edx/frontend-platform": "5.6.1",
|
"@edx/frontend-platform": "4.2.0",
|
||||||
"@edx/frontend-plugin-framework": "openedx/frontend-plugin-framework#jwesson/install-plugins",
|
"@edx/paragon": "^20.20.0",
|
||||||
"@edx/paragon": "^20.44.0",
|
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@pact-foundation/pact": "^11.0.2",
|
|
||||||
"classnames": "2.3.2",
|
"classnames": "2.3.2",
|
||||||
"core-js": "3.33.1",
|
"core-js": "3.25.5",
|
||||||
"history": "5.3.0",
|
|
||||||
"lodash.camelcase": "4.3.0",
|
"lodash.camelcase": "4.3.0",
|
||||||
"lodash.get": "4.4.2",
|
"lodash.get": "4.4.2",
|
||||||
"lodash.pick": "4.4.0",
|
"lodash.pick": "4.4.0",
|
||||||
"lodash.snakecase": "4.1.1",
|
"lodash.snakecase": "4.1.1",
|
||||||
"prop-types": "15.8.1",
|
"prop-types": "15.8.1",
|
||||||
"react": "17.0.2",
|
"react": "16.14.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "16.14.0",
|
||||||
"react-error-boundary": "^4.0.11",
|
|
||||||
"react-helmet": "6.1.0",
|
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.2.9",
|
||||||
"react-router": "6.16.0",
|
"react-router": "5.3.4",
|
||||||
"react-router-dom": "6.16.0",
|
"react-router-dom": "5.3.4",
|
||||||
"redux": "4.2.1",
|
"react-helmet": "6.1.0",
|
||||||
|
"redux": "4.2.0",
|
||||||
"redux-devtools-extension": "2.13.9",
|
"redux-devtools-extension": "2.13.9",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-saga": "1.2.3",
|
"redux-saga": "1.2.1",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "0.14.0",
|
"regenerator-runtime": "0.13.11",
|
||||||
"reselect": "4.1.8",
|
"reselect": "4.1.7",
|
||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "3.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "17.8.1",
|
"@commitlint/cli": "17.2.0",
|
||||||
"@commitlint/config-angular": "17.8.1",
|
"@commitlint/config-angular": "17.2.0",
|
||||||
"@edx/browserslist-config": "^1.1.1",
|
"@edx/browserslist-config": "^1.1.1",
|
||||||
"@edx/frontend-build": "13.0.4",
|
"@edx/reactifex": "2.1.1",
|
||||||
"@edx/reactifex": "2.2.0",
|
"@edx/frontend-build": "12.0.6",
|
||||||
"@testing-library/react": "12.1.5",
|
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "0.8.0",
|
|
||||||
"codecov": "3.8.3",
|
"codecov": "3.8.3",
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "3.11.0",
|
||||||
"glob": "10.3.10",
|
"enzyme-adapter-react-16": "1.15.7",
|
||||||
"react-test-renderer": "17.0.2",
|
"glob": "7.2.3",
|
||||||
|
"react-test-renderer": "16.14.0",
|
||||||
"reactifex": "1.1.1",
|
"reactifex": "1.1.1",
|
||||||
"redux-mock-store": "1.5.4"
|
"redux-mock-store": "1.5.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
'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,
|
|
||||||
};
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
'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,
|
|
||||||
};
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
/* 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: {},
|
|
||||||
};
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// 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';
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
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],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/* 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
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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';
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackagePatterns": ["@edx", "@openedx"],
|
"matchPackagePatterns": ["@edx"],
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"automerge": true
|
"automerge": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import { getConfig } from '@edx/frontend-platform';
|
|||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const Head = ({ intl }) => (
|
function Head({ intl }) {
|
||||||
<Helmet>
|
return (
|
||||||
<title>
|
<Helmet>
|
||||||
{intl.formatMessage(messages['profile.page.title'], { siteName: getConfig().SITE_NAME })}
|
<title>
|
||||||
</title>
|
{intl.formatMessage(messages['profile.page.title'], { siteName: getConfig().SITE_NAME })}
|
||||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
</title>
|
||||||
</Helmet>
|
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||||
);
|
</Helmet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Head.propTypes = {
|
Head.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
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 arMessages from './messages/ar.json';
|
||||||
import deMessages from './messages/de.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 frMessages from './messages/fr.json';
|
||||||
import hiMessages from './messages/hi.json';
|
import es419Messages from './messages/es_419.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';
|
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 frCAMessages from './messages/fr_CA.json';
|
||||||
// no need to import en messages-- they are in the defaultMessage field
|
// no need to import en messages-- they are in the defaultMessage field
|
||||||
|
|
||||||
const appMessages = {
|
const messages = {
|
||||||
ar: arMessages,
|
ar: arMessages,
|
||||||
'es-419': es419Messages,
|
'es-419': es419Messages,
|
||||||
'fa-ir': faIRMessages,
|
|
||||||
fr: frMessages,
|
fr: frMessages,
|
||||||
'zh-cn': zhcnMessages,
|
'zh-cn': zhcnMessages,
|
||||||
pt: ptMessages,
|
pt: ptMessages,
|
||||||
@@ -31,14 +23,6 @@ const appMessages = {
|
|||||||
'fr-ca': frCAMessages,
|
'fr-ca': frCAMessages,
|
||||||
ru: ruMessages,
|
ru: ruMessages,
|
||||||
uk: ukMessages,
|
uk: ukMessages,
|
||||||
'de-de': dedeCAMessages,
|
|
||||||
'it-it': ititCAMessages,
|
|
||||||
'pt-pt': ptptCAMessages,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default messages;
|
||||||
headerMessages,
|
|
||||||
footerMessages,
|
|
||||||
paragonMessages,
|
|
||||||
appMessages,
|
|
||||||
];
|
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "تم الحفظ",
|
"profile.formcontrols.button.saved": "تم الحفظ",
|
||||||
"profile.visibility.who.just.me": "أنا فقط",
|
"profile.visibility.who.just.me": "أنا فقط",
|
||||||
"profile.visibility.who.everyone": "جميع من على {siteName}",
|
"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.full.name": "الاسم الكامل",
|
||||||
"profile.name.details": "هذا هو الاسم الذي يظهر في حسابك وفي شهاداتك",
|
"profile.name.details": "هذا هو الاسم الذي يظهر في حسابك وفي شهاداتك",
|
||||||
"profile.name.empty": "إضافة الاسم",
|
"profile.name.empty": "إضافة الاسم",
|
||||||
|
|||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"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."
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"profile.page.title": "Perfil | {siteName}",
|
"profile.page.title": "Profile | {siteName}",
|
||||||
"profile.age.details": "Para compartir el perfil con otros {siteName} estudiantes, debe confirmar que es mayor de 13 años.",
|
"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.age.set.date": "Establece tu fecha de nacimiento",
|
||||||
"profile.datejoined.member.since": "Miembro desde {year}",
|
"profile.datejoined.member.since": "Miembro desde {year}",
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Guardado",
|
"profile.formcontrols.button.saved": "Guardado",
|
||||||
"profile.visibility.who.just.me": "Solo yo",
|
"profile.visibility.who.just.me": "Solo yo",
|
||||||
"profile.visibility.who.everyone": "Todos en {siteName}",
|
"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.full.name": "Nombre completo",
|
||||||
"profile.name.details": "Este es el nombre que aparecerá en tu cuenta y en tus certificados.",
|
"profile.name.details": "Este es el nombre que aparecerá en tu cuenta y en tus certificados.",
|
||||||
"profile.name.empty": "Añade nombre",
|
"profile.name.empty": "Añade nombre",
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"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} میتوانند ببینند."
|
|
||||||
}
|
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Enregistré",
|
"profile.formcontrols.button.saved": "Enregistré",
|
||||||
"profile.visibility.who.just.me": "Juste moi",
|
"profile.visibility.who.just.me": "Juste moi",
|
||||||
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
|
"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.full.name": "Nom complet",
|
||||||
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos certificats.",
|
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos certificats.",
|
||||||
"profile.name.empty": "Ajouter un nom",
|
"profile.name.empty": "Ajouter un nom",
|
||||||
|
|||||||
@@ -16,17 +16,17 @@
|
|||||||
"profile.country.label": "Adresse",
|
"profile.country.label": "Adresse",
|
||||||
"profile.country.empty": "Ajouter un emplacement",
|
"profile.country.empty": "Ajouter un emplacement",
|
||||||
"profile.education.empty": "Ajouter formation",
|
"profile.education.empty": "Ajouter formation",
|
||||||
"profile.education.education": "Formation",
|
"profile.education.education": "Education",
|
||||||
"profile.education.levels.p": "Doctorat",
|
"profile.education.levels.p": "Doctorat",
|
||||||
"profile.education.levels.m": "Maîtrise ou diplôme professionnel",
|
"profile.education.levels.m": "Maitrise ou diplôme professionnel",
|
||||||
"profile.education.levels.b": "Diplôme de baccalauréat",
|
"profile.education.levels.b": "Diplôme de baccalauréat",
|
||||||
"profile.education.levels.a": "Diplôme d'associé",
|
"profile.education.levels.a": "Diplôme d'associé",
|
||||||
"profile.education.levels.hs": "Lycée / enseignement secondaire",
|
"profile.education.levels.hs": "Lycée / enseignement secondaire",
|
||||||
"profile.education.levels.jhs": "Collège / enseignement secondaire inférieur",
|
"profile.education.levels.jhs": "Collège / enseignement secondaire inférieur",
|
||||||
"profile.education.levels.el": "Enseignement primaire",
|
"profile.education.levels.el": "Enseignement primaire",
|
||||||
"profile.education.levels.none": "Sans formation formelle",
|
"profile.education.levels.none": "Sans diplôme",
|
||||||
"profile.education.levels.o": "Autre niveau de formation",
|
"profile.education.levels.o": "Autre niveau d'étude",
|
||||||
"profile.editbutton.edit": "Éditer",
|
"profile.editbutton.edit": "Modifier",
|
||||||
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
|
"profile.formcontrols.who.can.see": "Qui peut voir ça :",
|
||||||
"profile.formcontrols.button.cancel": "Annuler",
|
"profile.formcontrols.button.cancel": "Annuler",
|
||||||
"profile.formcontrols.button.save": "Sauvegarder",
|
"profile.formcontrols.button.save": "Sauvegarder",
|
||||||
@@ -34,19 +34,14 @@
|
|||||||
"profile.formcontrols.button.saved": "Sauvegardé",
|
"profile.formcontrols.button.saved": "Sauvegardé",
|
||||||
"profile.visibility.who.just.me": "Juste moi",
|
"profile.visibility.who.just.me": "Juste moi",
|
||||||
"profile.visibility.who.everyone": "Tout le monde sur {siteName}",
|
"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.full.name": "Nom complet",
|
||||||
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos attestations.",
|
"profile.name.details": "C'est le nom qui apparaît dans votre compte et sur vos attestations.",
|
||||||
"profile.name.empty": "Ajouter un nom",
|
"profile.name.empty": "Ajouter un nom",
|
||||||
"profile.preferredlanguage.empty": "Ajouter une langue",
|
"profile.preferredlanguage.empty": "Ajouter une langue",
|
||||||
"profile.preferredlanguage.label": "Langue principale parlée",
|
"profile.preferredlanguage.label": "Langue principale parlée",
|
||||||
"profile.profileavatar.upload-button": "Téléverser une photo",
|
"profile.profileavatar.upload-button": "Téléverser une photo",
|
||||||
"profile.profileavatar.remove.button": "Retirer",
|
"profile.profileavatar.remove.button": "Supprimer",
|
||||||
"profile.image.alt.attribute": "avatar de profil",
|
"profile.image.alt.attribute": "Avatar de profil",
|
||||||
"profile.profileavatar.change-button": "Modifier",
|
"profile.profileavatar.change-button": "Modifier",
|
||||||
"profile.sociallinks.add": "Ajouter {network}",
|
"profile.sociallinks.add": "Ajouter {network}",
|
||||||
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",
|
"profile.sociallinks.social.links": "Liens vers les réseaux sociaux",
|
||||||
|
|||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"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}."
|
|
||||||
}
|
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"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}."
|
|
||||||
}
|
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"profile.certificate.organization.label": "From",
|
"profile.certificate.organization.label": "From",
|
||||||
"profile.certificate.completion.date.label": "Completed on {date}",
|
"profile.certificate.completion.date.label": "Completed on {date}",
|
||||||
"profile.no.certificates": "You don't have any certificates yet.",
|
"profile.no.certificates": "You don't have any certificates yet.",
|
||||||
"profile.certificates.my.certificates": "Мої сертифікати",
|
"profile.certificates.my.certificates": "My Certificates",
|
||||||
"profile.certificates.view.certificate": "View Certificate",
|
"profile.certificates.view.certificate": "View Certificate",
|
||||||
"profile.certificates.types.verified": "Verified Certificate",
|
"profile.certificates.types.verified": "Verified Certificate",
|
||||||
"profile.certificates.types.professional": "Professional Certificate",
|
"profile.certificates.types.professional": "Professional Certificate",
|
||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -34,11 +34,6 @@
|
|||||||
"profile.formcontrols.button.saved": "Saved",
|
"profile.formcontrols.button.saved": "Saved",
|
||||||
"profile.visibility.who.just.me": "Just me",
|
"profile.visibility.who.just.me": "Just me",
|
||||||
"profile.visibility.who.everyone": "Everyone on {siteName}",
|
"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.full.name": "Full Name",
|
||||||
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
"profile.name.details": "This is the name that appears in your account and on your certificates.",
|
||||||
"profile.name.empty": "Add name",
|
"profile.name.empty": "Add name",
|
||||||
|
|||||||
@@ -15,38 +15,31 @@ import {
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
import Header from '@edx/frontend-component-header';
|
import Header, { messages as headerMessages } from '@edx/frontend-component-header';
|
||||||
import Footer from '@edx/frontend-component-footer';
|
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||||
|
|
||||||
import messages from './i18n';
|
import appMessages from './i18n';
|
||||||
|
import { ProfilePage, NotFoundPage } from './profile';
|
||||||
import configureStore from './data/configureStore';
|
import configureStore from './data/configureStore';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import Head from './head/Head';
|
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, () => {
|
subscribe(APP_READY, () => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppProvider store={configureStore()}>
|
<AppProvider store={configureStore()}>
|
||||||
<Head />
|
<Head />
|
||||||
<RenderHeader />
|
<Header />
|
||||||
<main id="main">
|
<main>
|
||||||
<AppRoutes />
|
<Switch>
|
||||||
|
<Route path="/u/:username" component={ProfilePage} />
|
||||||
|
<Route path="/notfound" component={NotFoundPage} />
|
||||||
|
<Route path="*" component={NotFoundPage} />
|
||||||
|
</Switch>
|
||||||
</main>
|
</main>
|
||||||
<RenderFooter />
|
<Footer />
|
||||||
</AppProvider>,
|
</AppProvider>,
|
||||||
document.getElementById('root'),
|
document.getElementById('root'),
|
||||||
);
|
);
|
||||||
@@ -57,13 +50,19 @@ subscribe(APP_INIT_ERROR, (error) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
initialize({
|
initialize({
|
||||||
messages,
|
messages: [
|
||||||
|
appMessages,
|
||||||
|
headerMessages,
|
||||||
|
footerMessages,
|
||||||
|
],
|
||||||
|
requireAuthenticatedUser: true,
|
||||||
hydrateAuthenticatedUser: true,
|
hydrateAuthenticatedUser: true,
|
||||||
handlers: {
|
handlers: {
|
||||||
config: () => {
|
config: () => {
|
||||||
mergeConfig({
|
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,
|
COLLECT_YEAR_OF_BIRTH: process.env.COLLECT_YEAR_OF_BIRTH,
|
||||||
ENABLE_SKILLS_BUILDER_PROFILE: process.env.ENABLE_SKILLS_BUILDER_PROFILE,
|
|
||||||
}, 'App loadConfig override handler');
|
}, 'App loadConfig override handler');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,33 +4,35 @@ import { Alert } from '@edx/paragon';
|
|||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
const AgeMessage = ({ accountSettingsUrl }) => (
|
function AgeMessage({ accountSettingsUrl }) {
|
||||||
<Alert
|
return (
|
||||||
variant="info"
|
<Alert
|
||||||
dismissible={false}
|
variant="info"
|
||||||
show
|
dismissible={false}
|
||||||
>
|
show
|
||||||
<Alert.Heading id="profile.age.headline">
|
>
|
||||||
Your profile cannot be shared.
|
<Alert.Heading id="profile.age.headline">
|
||||||
</Alert.Heading>
|
Your profile cannot be shared.
|
||||||
<FormattedMessage
|
</Alert.Heading>
|
||||||
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
|
<FormattedMessage
|
||||||
id="profile.age.set.date"
|
id="profile.age.details"
|
||||||
defaultMessage="Set your date of birth"
|
defaultMessage="To share your profile with other {siteName} learners, you must confirm that you are over the age of 13."
|
||||||
description="Label on a link to set birthday"
|
description="Error message"
|
||||||
|
tagName="p"
|
||||||
|
values={{
|
||||||
|
siteName: getConfig().SITE_NAME,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Alert.Link>
|
<Alert.Link href={accountSettingsUrl}>
|
||||||
</Alert>
|
<FormattedMessage
|
||||||
);
|
id="profile.age.set.date"
|
||||||
|
defaultMessage="Set your date of birth"
|
||||||
|
description="Label on a link to set birthday"
|
||||||
|
/>
|
||||||
|
</Alert.Link>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
AgeMessage.propTypes = {
|
AgeMessage.propTypes = {
|
||||||
accountSettingsUrl: PropTypes.string.isRequired,
|
accountSettingsUrl: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Banner = () => <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
|
function Banner() {
|
||||||
|
return <div className="profile-page-bg-banner bg-primary d-none d-md-block p-relative" />;
|
||||||
|
}
|
||||||
|
|
||||||
export default Banner;
|
export default Banner;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const DateJoined = ({ date }) => {
|
function DateJoined({ date }) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className="mb-0">
|
<p className="mb-0">
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -18,7 +19,7 @@ const DateJoined = ({ date }) => {
|
|||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
DateJoined.propTypes = {
|
DateJoined.propTypes = {
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const NotFoundPage = () => (
|
export default function NotFoundPage() {
|
||||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
return (
|
||||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||||
<FormattedMessage
|
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||||
id="profile.notfound.message"
|
<FormattedMessage
|
||||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
id="profile.notfound.message"
|
||||||
description="error message when a page does not exist"
|
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>
|
</p>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
export default NotFoundPage;
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import DateJoined from './DateJoined';
|
|||||||
import UsernameDescription from './UsernameDescription';
|
import UsernameDescription from './UsernameDescription';
|
||||||
import PageLoading from './PageLoading';
|
import PageLoading from './PageLoading';
|
||||||
import Banner from './Banner';
|
import Banner from './Banner';
|
||||||
import LearningGoal from './forms/LearningGoal';
|
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
import { profilePageSelector } from './data/selectors';
|
import { profilePageSelector } from './data/selectors';
|
||||||
@@ -41,18 +40,16 @@ import { profilePageSelector } from './data/selectors';
|
|||||||
// i18n
|
// i18n
|
||||||
import messages from './ProfilePage.messages';
|
import messages from './ProfilePage.messages';
|
||||||
|
|
||||||
import withParams from '../utils/hoc';
|
|
||||||
|
|
||||||
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
|
ensureConfig(['CREDENTIALS_BASE_URL', 'LMS_BASE_URL'], 'ProfilePage');
|
||||||
|
|
||||||
class ProfilePage extends React.Component {
|
class ProfilePage extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
const credentialsBaseUrl = context.config.CREDENTIALS_BASE_URL;
|
const recordsUrl = this.getRecordsUrl(context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
viewMyRecordsUrl: credentialsBaseUrl ? `${credentialsBaseUrl}/records` : null,
|
viewMyRecordsUrl: recordsUrl,
|
||||||
accountSettingsUrl: `${context.config.LMS_BASE_URL}/account/settings`,
|
accountSettingsUrl: `${context.config.LMS_BASE_URL}/account/settings`,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,9 +62,9 @@ class ProfilePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchProfile(this.props.params.username);
|
this.props.fetchProfile(this.props.match.params.username);
|
||||||
sendTrackingLogEvent('edx.profile.viewed', {
|
sendTrackingLogEvent('edx.profile.viewed', {
|
||||||
username: this.props.params.username,
|
username: this.props.match.params.username,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +92,19 @@ class ProfilePage extends React.Component {
|
|||||||
this.props.updateDraft(name, value);
|
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() {
|
isYOBDisabled() {
|
||||||
const { yearOfBirth } = this.props;
|
const { yearOfBirth } = this.props;
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
@@ -104,7 +114,7 @@ class ProfilePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAuthenticatedUserProfile() {
|
isAuthenticatedUserProfile() {
|
||||||
return this.props.params.username === this.context.authenticatedUser.username;
|
return this.props.match.params.username === this.context.authenticatedUser.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inserted into the DOM in two places (for responsive layout)
|
// Inserted into the DOM in two places (for responsive layout)
|
||||||
@@ -126,7 +136,7 @@ class ProfilePage extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span data-hj-suppress>
|
<span data-hj-suppress>
|
||||||
<h1 className="h2 mb-0 font-weight-bold">{this.props.params.username}</h1>
|
<h1 className="h2 mb-0 font-weight-bold">{this.props.match.params.username}</h1>
|
||||||
<DateJoined date={dateJoined} />
|
<DateJoined date={dateJoined} />
|
||||||
{this.isYOBDisabled() && <UsernameDescription />}
|
{this.isYOBDisabled() && <UsernameDescription />}
|
||||||
<hr className="d-none d-md-block" />
|
<hr className="d-none d-md-block" />
|
||||||
@@ -174,8 +184,6 @@ class ProfilePage extends React.Component {
|
|||||||
socialLinks,
|
socialLinks,
|
||||||
draftSocialLinksByPlatform,
|
draftSocialLinksByPlatform,
|
||||||
visibilitySocialLinks,
|
visibilitySocialLinks,
|
||||||
learningGoal,
|
|
||||||
visibilityLearningGoal,
|
|
||||||
languageProficiencies,
|
languageProficiencies,
|
||||||
visibilityLanguageProficiencies,
|
visibilityLanguageProficiencies,
|
||||||
visibilityCourseCertificates,
|
visibilityCourseCertificates,
|
||||||
@@ -212,7 +220,6 @@ class ProfilePage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>PluginPOC</div>
|
|
||||||
<div className="col pl-0">
|
<div className="col pl-0">
|
||||||
<div className="d-md-none">
|
<div className="d-md-none">
|
||||||
{this.renderHeadingLockup()}
|
{this.renderHeadingLockup()}
|
||||||
@@ -271,14 +278,6 @@ class ProfilePage extends React.Component {
|
|||||||
formId="bio"
|
formId="bio"
|
||||||
{...commonFormProps}
|
{...commonFormProps}
|
||||||
/>
|
/>
|
||||||
{getConfig().ENABLE_SKILLS_BUILDER_PROFILE && (
|
|
||||||
<LearningGoal
|
|
||||||
learningGoal={learningGoal}
|
|
||||||
visibilityLearningGoal={visibilityLearningGoal}
|
|
||||||
formId="learningGoal"
|
|
||||||
{...commonFormProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Certificates
|
<Certificates
|
||||||
visibilityCourseCertificates={visibilityCourseCertificates}
|
visibilityCourseCertificates={visibilityCourseCertificates}
|
||||||
formId="certificates"
|
formId="certificates"
|
||||||
@@ -347,10 +346,6 @@ ProfilePage.propTypes = {
|
|||||||
})),
|
})),
|
||||||
visibilitySocialLinks: PropTypes.string.isRequired,
|
visibilitySocialLinks: PropTypes.string.isRequired,
|
||||||
|
|
||||||
// Learning Goal form data
|
|
||||||
learningGoal: PropTypes.string,
|
|
||||||
visibilityLearningGoal: PropTypes.string.isRequired,
|
|
||||||
|
|
||||||
// Other data we need
|
// Other data we need
|
||||||
profileImage: PropTypes.shape({
|
profileImage: PropTypes.shape({
|
||||||
src: PropTypes.string,
|
src: PropTypes.string,
|
||||||
@@ -373,8 +368,10 @@ ProfilePage.propTypes = {
|
|||||||
updateDraft: PropTypes.func.isRequired,
|
updateDraft: PropTypes.func.isRequired,
|
||||||
|
|
||||||
// Router
|
// Router
|
||||||
params: PropTypes.shape({
|
match: PropTypes.shape({
|
||||||
username: PropTypes.string.isRequired,
|
params: PropTypes.shape({
|
||||||
|
username: PropTypes.string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
@@ -393,7 +390,6 @@ ProfilePage.defaultProps = {
|
|||||||
socialLinks: [],
|
socialLinks: [],
|
||||||
draftSocialLinksByPlatform: {},
|
draftSocialLinksByPlatform: {},
|
||||||
bio: null,
|
bio: null,
|
||||||
learningGoal: null,
|
|
||||||
languageProficiencies: [],
|
languageProficiencies: [],
|
||||||
courseCertificates: null,
|
courseCertificates: null,
|
||||||
requiresParentalConsent: null,
|
requiresParentalConsent: null,
|
||||||
@@ -411,4 +407,4 @@ export default connect(
|
|||||||
closeForm,
|
closeForm,
|
||||||
updateDraft,
|
updateDraft,
|
||||||
},
|
},
|
||||||
)(injectIntl(withParams(ProfilePage)));
|
)(injectIntl(ProfilePage));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const requiredProfilePageProps = {
|
|||||||
deleteProfilePhoto: () => {},
|
deleteProfilePhoto: () => {},
|
||||||
openField: () => {},
|
openField: () => {},
|
||||||
closeField: () => {},
|
closeField: () => {},
|
||||||
params: { username: 'staff' },
|
match: { params: { username: 'staff' } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock language cookie
|
// Mock language cookie
|
||||||
@@ -66,29 +66,32 @@ beforeEach(() => {
|
|||||||
analytics.sendTrackingLogEvent.mockReset();
|
analytics.sendTrackingLogEvent.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
const ProfilePageWrapper = ({
|
function ProfilePageWrapper({
|
||||||
contextValue, store, params, requiresParentalConsent,
|
contextValue, store, match, requiresParentalConsent,
|
||||||
}) => (
|
}) {
|
||||||
<AppContext.Provider
|
return (
|
||||||
value={contextValue}
|
<AppContext.Provider
|
||||||
>
|
value={contextValue}
|
||||||
<IntlProvider locale="en">
|
>
|
||||||
<Provider store={store}>
|
<IntlProvider locale="en">
|
||||||
<ProfilePage {...requiredProfilePageProps} params={params} requiresParentalConsent={requiresParentalConsent} />
|
<Provider store={store}>
|
||||||
</Provider>
|
<ProfilePage {...requiredProfilePageProps} match={match} requiresParentalConsent={requiresParentalConsent} />
|
||||||
</IntlProvider>
|
</Provider>
|
||||||
</AppContext.Provider>
|
</IntlProvider>
|
||||||
);
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ProfilePageWrapper.defaultProps = {
|
ProfilePageWrapper.defaultProps = {
|
||||||
params: { username: 'staff' },
|
match: { params: { username: 'staff' } },
|
||||||
requiresParentalConsent: null,
|
requiresParentalConsent: null,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfilePageWrapper.propTypes = {
|
ProfilePageWrapper.propTypes = {
|
||||||
contextValue: PropTypes.shape({}).isRequired,
|
contextValue: PropTypes.shape({}).isRequired,
|
||||||
store: PropTypes.shape({}).isRequired,
|
store: PropTypes.shape({}).isRequired,
|
||||||
params: PropTypes.shape({}),
|
match: PropTypes.shape({}),
|
||||||
requiresParentalConsent: PropTypes.bool,
|
requiresParentalConsent: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,7 +126,7 @@ describe('<ProfilePage />', () => {
|
|||||||
<ProfilePageWrapper
|
<ProfilePageWrapper
|
||||||
contextValue={contextValue}
|
contextValue={contextValue}
|
||||||
store={mockStore(storeMocks.viewOtherProfile)}
|
store={mockStore(storeMocks.viewOtherProfile)}
|
||||||
params={{ username: 'verified' }} // Override default params
|
match={{ params: { username: 'verified' } }} // Override default match
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const tree = renderer.create(component).toJSON();
|
const tree = renderer.create(component).toJSON();
|
||||||
@@ -278,7 +281,7 @@ describe('<ProfilePage />', () => {
|
|||||||
<ProfilePageWrapper
|
<ProfilePageWrapper
|
||||||
contextValue={contextValue}
|
contextValue={contextValue}
|
||||||
store={mockStore(storeMocks.loadingApp)}
|
store={mockStore(storeMocks.loadingApp)}
|
||||||
params={{ username: 'test-username' }}
|
match={{ params: { username: 'test-username' } }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const wrapper = mount(component);
|
const wrapper = mount(component);
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
/* 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)));
|
|
||||||
@@ -4,20 +4,22 @@ import { VisibilityOff } from '@edx/paragon/icons';
|
|||||||
import { Icon } from '@edx/paragon';
|
import { Icon } from '@edx/paragon';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
const UsernameDescription = () => (
|
function UsernameDescription() {
|
||||||
<div className="d-flex align-items-center mt-3 mb-2rem">
|
return (
|
||||||
<Icon src={VisibilityOff} className="icon-visibility-off" />
|
<div className="d-flex align-items-center mt-3 mb-2rem">
|
||||||
<div className="username-description">
|
<Icon src={VisibilityOff} className="icon-visibility-off" />
|
||||||
<FormattedMessage
|
<div className="username-description">
|
||||||
id="profile.username.description"
|
<FormattedMessage
|
||||||
defaultMessage="Your profile information is only visible to you. Only your username is visible to others on {siteName}."
|
id="profile.username.description"
|
||||||
description="A description of the username field"
|
defaultMessage="Your profile information is only visible to you. Only your username is visible to others on {siteName}."
|
||||||
values={{
|
description="A description of the username field"
|
||||||
siteName: getConfig().SITE_NAME,
|
values={{
|
||||||
}}
|
siteName: getConfig().SITE_NAME,
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
export default UsernameDescription;
|
export default UsernameDescription;
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ module.exports = {
|
|||||||
imageUrlMedium: null,
|
imageUrlMedium: null,
|
||||||
imageUrlLarge: null
|
imageUrlLarge: null
|
||||||
},
|
},
|
||||||
levelOfEducation: null,
|
levelOfEducation: null
|
||||||
learningGoal: null
|
|
||||||
},
|
},
|
||||||
profilePage: {
|
profilePage: {
|
||||||
errors: {},
|
errors: {},
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ module.exports = {
|
|||||||
secondaryEmail: null,
|
secondaryEmail: null,
|
||||||
timeZone: null,
|
timeZone: null,
|
||||||
gender: null,
|
gender: null,
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
learningGoal: null,
|
|
||||||
},
|
},
|
||||||
profilePage: {
|
profilePage: {
|
||||||
errors: {},
|
errors: {},
|
||||||
@@ -92,8 +91,7 @@ module.exports = {
|
|||||||
timeZone: null,
|
timeZone: null,
|
||||||
levelOfEducation: 'el',
|
levelOfEducation: 'el',
|
||||||
gender: null,
|
gender: null,
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
learningGoal: null,
|
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
visibilityUserLocation: 'all_users',
|
visibilityUserLocation: 'all_users',
|
||||||
@@ -106,8 +104,7 @@ module.exports = {
|
|||||||
visibilityName: 'private',
|
visibilityName: 'private',
|
||||||
visibilityLanguageProficiencies: 'all_users',
|
visibilityLanguageProficiencies: 'all_users',
|
||||||
visibilityCountry: 'all_users',
|
visibilityCountry: 'all_users',
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
visibilityLearningGoal: 'private',
|
|
||||||
},
|
},
|
||||||
courseCertificates: [
|
courseCertificates: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ module.exports = {
|
|||||||
secondaryEmail: null,
|
secondaryEmail: null,
|
||||||
timeZone: null,
|
timeZone: null,
|
||||||
gender: null,
|
gender: null,
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
learningGoal: 'advance_career',
|
|
||||||
},
|
},
|
||||||
profilePage: {
|
profilePage: {
|
||||||
errors: {},
|
errors: {},
|
||||||
@@ -84,8 +83,7 @@ module.exports = {
|
|||||||
preferences: {},
|
preferences: {},
|
||||||
courseCertificates: [],
|
courseCertificates: [],
|
||||||
drafts: {},
|
drafts: {},
|
||||||
isLoadingProfile: false,
|
isLoadingProfile: false
|
||||||
learningGoal: 'advance_career',
|
|
||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
location: {
|
location: {
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ module.exports = {
|
|||||||
secondaryEmail: null,
|
secondaryEmail: null,
|
||||||
timeZone: null,
|
timeZone: null,
|
||||||
gender: null,
|
gender: null,
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
learningGoal: 'advance_career'
|
|
||||||
},
|
},
|
||||||
profilePage: {
|
profilePage: {
|
||||||
errors: {},
|
errors: {},
|
||||||
@@ -92,8 +91,7 @@ module.exports = {
|
|||||||
timeZone: null,
|
timeZone: null,
|
||||||
levelOfEducation: 'el',
|
levelOfEducation: 'el',
|
||||||
gender: null,
|
gender: null,
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
learningGoal: 'advance_career'
|
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
visibilityUserLocation: 'all_users',
|
visibilityUserLocation: 'all_users',
|
||||||
@@ -106,8 +104,7 @@ module.exports = {
|
|||||||
visibilityName: 'private',
|
visibilityName: 'private',
|
||||||
visibilityLanguageProficiencies: 'all_users',
|
visibilityLanguageProficiencies: 'all_users',
|
||||||
visibilityCountry: 'all_users',
|
visibilityCountry: 'all_users',
|
||||||
accountPrivacy: 'custom',
|
accountPrivacy: 'custom'
|
||||||
visibilityLearningGoal: 'private',
|
|
||||||
},
|
},
|
||||||
courseCertificates: [
|
courseCertificates: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -103,9 +103,6 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -166,7 +163,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -244,7 +241,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -1652,9 +1649,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
||||||
id="country-2"
|
id="country-2"
|
||||||
>
|
>
|
||||||
<div>
|
country error
|
||||||
country error
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -1750,7 +1745,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -2392,7 +2387,7 @@ exports[`<ProfilePage /> Renders correctly in various states test country edit w
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -2489,9 +2484,6 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -2552,7 +2544,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -2630,7 +2622,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -3012,9 +3004,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
||||||
id="levelOfEducation-3"
|
id="levelOfEducation-3"
|
||||||
>
|
>
|
||||||
<div>
|
education error
|
||||||
education error
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -3110,7 +3100,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -3572,7 +3562,7 @@ exports[`<ProfilePage /> Renders correctly in various states test education edit
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -3669,9 +3659,6 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -3732,7 +3719,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -3810,7 +3797,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -4977,9 +4964,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
||||||
id="languageProficiencies-4"
|
id="languageProficiencies-4"
|
||||||
>
|
>
|
||||||
<div>
|
preferred language error
|
||||||
preferred language error
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -5075,7 +5060,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -5627,7 +5612,7 @@ exports[`<ProfilePage /> Renders correctly in various states test preferreded la
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -5701,9 +5686,6 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -5741,7 +5723,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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.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"
|
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"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -5802,7 +5784,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing other profi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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.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"
|
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"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -5959,9 +5941,6 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -6022,7 +6001,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -6100,7 +6079,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -6915,7 +6894,7 @@ exports[`<ProfilePage /> Renders correctly in various states viewing own profile
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -7012,9 +6991,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -7075,7 +7051,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -7153,7 +7129,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -7842,7 +7818,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -8035,7 +8011,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -8132,9 +8108,6 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -8195,7 +8168,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -8273,7 +8246,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -8872,9 +8845,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
||||||
id="bio-1"
|
id="bio-1"
|
||||||
>
|
>
|
||||||
<div>
|
bio error
|
||||||
bio error
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -8970,7 +8941,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -9163,7 +9134,7 @@ exports[`<ProfilePage /> Renders correctly in various states while saving an edi
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -9260,9 +9231,6 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
PluginPOC
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="col pl-0"
|
className="col pl-0"
|
||||||
>
|
>
|
||||||
@@ -10126,7 +10094,7 @@ exports[`<ProfilePage /> Renders correctly in various states without credentials
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
const mockData = {
|
|
||||||
learningGoal: 'advance_career',
|
|
||||||
editMode: 'static',
|
|
||||||
visibilityLearningGoal: 'private',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mockData;
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// 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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
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));
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
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']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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));
|
|
||||||
@@ -244,12 +244,14 @@ export default connect(
|
|||||||
{},
|
{},
|
||||||
)(injectIntl(SocialLinks));
|
)(injectIntl(SocialLinks));
|
||||||
|
|
||||||
const SocialLink = ({ url, name, platform }) => (
|
function SocialLink({ url, name, platform }) {
|
||||||
<a href={url} className="font-weight-bold">
|
return (
|
||||||
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
|
<a href={url} className="font-weight-bold">
|
||||||
{name}
|
<FontAwesomeIcon className="mr-2" icon={platformDisplayInfo[platform].icon} />
|
||||||
</a>
|
{name}
|
||||||
);
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
SocialLink.propTypes = {
|
SocialLink.propTypes = {
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
@@ -257,9 +259,9 @@ SocialLink.propTypes = {
|
|||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditableListItem = ({
|
function EditableListItem({
|
||||||
url, platform, onClickEmptyContent, name,
|
url, platform, onClickEmptyContent, name,
|
||||||
}) => {
|
}) {
|
||||||
const linkDisplay = url ? (
|
const linkDisplay = url ? (
|
||||||
<SocialLink name={name} url={url} platform={platform} />
|
<SocialLink name={name} url={url} platform={platform} />
|
||||||
) : (
|
) : (
|
||||||
@@ -267,7 +269,7 @@ const EditableListItem = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return <li className="form-group">{linkDisplay}</li>;
|
return <li className="form-group">{linkDisplay}</li>;
|
||||||
};
|
}
|
||||||
|
|
||||||
EditableListItem.propTypes = {
|
EditableListItem.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
@@ -280,22 +282,24 @@ EditableListItem.defaultProps = {
|
|||||||
onClickEmptyContent: null,
|
onClickEmptyContent: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditingListItem = ({
|
function EditingListItem({
|
||||||
platform, name, value, onChange, error,
|
platform, name, value, onChange, error,
|
||||||
}) => (
|
}) {
|
||||||
<li className="form-group">
|
return (
|
||||||
<label htmlFor={`social-${platform}`}>{name}</label>
|
<li className="form-group">
|
||||||
<input
|
<label htmlFor={`social-${platform}`}>{name}</label>
|
||||||
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
|
<input
|
||||||
type="text"
|
className={classNames('form-control', { 'is-invalid': Boolean(error) })}
|
||||||
id={`social-${platform}`}
|
type="text"
|
||||||
name={platform}
|
id={`social-${platform}`}
|
||||||
value={value || ''}
|
name={platform}
|
||||||
onChange={onChange}
|
value={value || ''}
|
||||||
aria-describedby="social-error-feedback"
|
onChange={onChange}
|
||||||
/>
|
aria-describedby="social-error-feedback"
|
||||||
</li>
|
/>
|
||||||
);
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
EditingListItem.propTypes = {
|
EditingListItem.propTypes = {
|
||||||
platform: PropTypes.string.isRequired,
|
platform: PropTypes.string.isRequired,
|
||||||
@@ -310,31 +314,35 @@ EditingListItem.defaultProps = {
|
|||||||
error: null,
|
error: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmptyListItem = ({ onClick, name }) => (
|
function EmptyListItem({ onClick, name }) {
|
||||||
<li className="mb-4">
|
return (
|
||||||
<EmptyContent onClick={onClick}>
|
<li className="mb-4">
|
||||||
<FormattedMessage
|
<EmptyContent onClick={onClick}>
|
||||||
id="profile.sociallinks.add"
|
<FormattedMessage
|
||||||
defaultMessage="Add {network}"
|
id="profile.sociallinks.add"
|
||||||
values={{
|
defaultMessage="Add {network}"
|
||||||
network: name,
|
values={{
|
||||||
}}
|
network: name,
|
||||||
description="{network} is the name of a social network such as Facebook or Twitter"
|
}}
|
||||||
/>
|
description="{network} is the name of a social network such as Facebook or Twitter"
|
||||||
</EmptyContent>
|
/>
|
||||||
</li>
|
</EmptyContent>
|
||||||
);
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
EmptyListItem.propTypes = {
|
EmptyListItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const StaticListItem = ({ name, url, platform }) => (
|
function StaticListItem({ name, url, platform }) {
|
||||||
<li className="mb-2">
|
return (
|
||||||
<SocialLink name={name} url={url} platform={platform} />
|
<li className="mb-2">
|
||||||
</li>
|
<SocialLink name={name} url={url} platform={platform} />
|
||||||
);
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
StaticListItem.propTypes = {
|
StaticListItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ configureI18n({
|
|||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
|
||||||
const SocialLinksWrapper = (props) => {
|
function SocialLinksWrapper(props) {
|
||||||
const contextValue = useMemo(() => ({
|
const contextValue = useMemo(() => ({
|
||||||
authenticatedUser: { userId: null, username: null, administrator: false },
|
authenticatedUser: { userId: null, username: null, administrator: false },
|
||||||
config: getConfig(),
|
config: getConfig(),
|
||||||
@@ -63,7 +63,7 @@ const SocialLinksWrapper = (props) => {
|
|||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
SocialLinksWrapper.defaultProps = {
|
SocialLinksWrapper.defaultProps = {
|
||||||
store: mockStore(savingEditedBio),
|
store: mockStore(savingEditedBio),
|
||||||
@@ -73,7 +73,7 @@ SocialLinksWrapper.propTypes = {
|
|||||||
store: PropTypes.shape({}),
|
store: PropTypes.shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const SocialLinksWrapperWithStore = ({ store }) => {
|
function SocialLinksWrapperWithStore({ store }) {
|
||||||
const contextValue = useMemo(() => ({
|
const contextValue = useMemo(() => ({
|
||||||
authenticatedUser: { userId: null, username: null, administrator: false },
|
authenticatedUser: { userId: null, username: null, administrator: false },
|
||||||
config: getConfig(),
|
config: getConfig(),
|
||||||
@@ -89,7 +89,7 @@ const SocialLinksWrapperWithStore = ({ store }) => {
|
|||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</AppContext.Provider>
|
</AppContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
SocialLinksWrapperWithStore.defaultProps = {
|
SocialLinksWrapperWithStore.defaultProps = {
|
||||||
store: mockStore(savingEditedBio),
|
store: mockStore(savingEditedBio),
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ exports[`<SocialLinks /> calls social links with edit mode bio 1`] = `
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M22 12A10 10 0 1 1 6.122 3.91l1.176 1.618A8 8 0 1 0 20 12h2Z"
|
d="M22 12A10 10 0 116.122 3.91l1.176 1.618A8 8 0 1020 12h2z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -7,20 +7,22 @@ import { Button } from '@edx/paragon';
|
|||||||
|
|
||||||
import messages from './EditButton.messages';
|
import messages from './EditButton.messages';
|
||||||
|
|
||||||
const EditButton = ({
|
function EditButton({
|
||||||
onClick, className, style, intl,
|
onClick, className, style, intl,
|
||||||
}) => (
|
}) {
|
||||||
<Button
|
return (
|
||||||
variant="link"
|
<Button
|
||||||
size="sm"
|
variant="link"
|
||||||
className={className}
|
size="sm"
|
||||||
onClick={onClick}
|
className={className}
|
||||||
style={style}
|
onClick={onClick}
|
||||||
>
|
style={style}
|
||||||
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />
|
>
|
||||||
{intl.formatMessage(messages['profile.editbutton.edit'])}
|
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />
|
||||||
</Button>
|
{intl.formatMessage(messages['profile.editbutton.edit'])}
|
||||||
);
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default injectIntl(EditButton);
|
export default injectIntl(EditButton);
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,24 @@ import PropTypes from 'prop-types';
|
|||||||
import EditButton from './EditButton';
|
import EditButton from './EditButton';
|
||||||
import { Visibility } from './Visibility';
|
import { Visibility } from './Visibility';
|
||||||
|
|
||||||
const EditableItemHeader = ({
|
function EditableItemHeader({
|
||||||
content,
|
content,
|
||||||
showVisibility,
|
showVisibility,
|
||||||
visibility,
|
visibility,
|
||||||
showEditButton,
|
showEditButton,
|
||||||
onClickEdit,
|
onClickEdit,
|
||||||
headingId,
|
headingId,
|
||||||
}) => (
|
}) {
|
||||||
<div className="editable-item-header mb-2">
|
return (
|
||||||
<h2 className="edit-section-header" id={headingId}>
|
<div className="editable-item-header mb-2">
|
||||||
{content}
|
<h2 className="edit-section-header" id={headingId}>
|
||||||
{showEditButton ? <EditButton style={{ marginTop: '-.35rem' }} className="float-right px-0" onClick={onClickEdit} /> : null}
|
{content}
|
||||||
</h2>
|
{showEditButton ? <EditButton style={{ marginTop: '-.35rem' }} className="float-right px-0" onClick={onClickEdit} /> : null}
|
||||||
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
|
</h2>
|
||||||
</div>
|
{showVisibility ? <p className="mb-0"><Visibility to={visibility} /></p> : null}
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default EditableItemHeader;
|
export default EditableItemHeader;
|
||||||
|
|
||||||
|
|||||||
@@ -3,22 +3,24 @@ import PropTypes from 'prop-types';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
const EmptyContent = ({ children, onClick, showPlusIcon }) => (
|
function EmptyContent({ children, onClick, showPlusIcon }) {
|
||||||
<div>
|
return (
|
||||||
{onClick ? (
|
<div>
|
||||||
<button
|
{onClick ? (
|
||||||
type="button"
|
<button
|
||||||
className="pl-0 text-left btn btn-link"
|
type="button"
|
||||||
onClick={onClick}
|
className="pl-0 text-left btn btn-link"
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter') { onClick(); } }}
|
onClick={onClick}
|
||||||
tabIndex={0}
|
onKeyDown={(e) => { if (e.key === 'Enter') { onClick(); } }}
|
||||||
>
|
tabIndex={0}
|
||||||
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
|
>
|
||||||
{children}
|
{showPlusIcon ? <FontAwesomeIcon size="xs" className="mr-2" icon={faPlus} /> : null}
|
||||||
</button>
|
{children}
|
||||||
) : children}
|
</button>
|
||||||
</div>
|
) : children}
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default EmptyContent;
|
export default EmptyContent;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import messages from './FormControls.messages';
|
|||||||
|
|
||||||
import { VisibilitySelect } from './Visibility';
|
import { VisibilitySelect } from './Visibility';
|
||||||
|
|
||||||
const FormControls = ({
|
function FormControls({
|
||||||
cancelHandler, changeHandler, visibility, visibilityId, saveState, intl,
|
cancelHandler, changeHandler, visibility, visibilityId, saveState, intl,
|
||||||
}) => {
|
}) {
|
||||||
// Eliminate error/failed state for save button
|
// Eliminate error/failed state for save button
|
||||||
const buttonState = saveState === 'error' ? null : saveState;
|
const buttonState = saveState === 'error' ? null : saveState;
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ const FormControls = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default injectIntl(FormControls);
|
export default injectIntl(FormControls);
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const onChildExit = (htmlNode) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const SwitchContent = ({ expression, cases, className }) => {
|
function SwitchContent({ expression, cases, className }) {
|
||||||
const getContent = (caseKey) => {
|
const getContent = (caseKey) => {
|
||||||
if (cases[caseKey]) {
|
if (cases[caseKey]) {
|
||||||
if (typeof cases[caseKey] === 'string') {
|
if (typeof cases[caseKey] === 'string') {
|
||||||
@@ -48,7 +48,7 @@ const SwitchContent = ({ expression, cases, className }) => {
|
|||||||
{getContent(expression)}
|
{getContent(expression)}
|
||||||
</TransitionReplace>
|
</TransitionReplace>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
SwitchContent.propTypes = {
|
SwitchContent.propTypes = {
|
||||||
expression: PropTypes.string,
|
expression: PropTypes.string,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { faEyeSlash, faEye } from '@fortawesome/free-regular-svg-icons';
|
|||||||
|
|
||||||
import messages from './Visibility.messages';
|
import messages from './Visibility.messages';
|
||||||
|
|
||||||
const Visibility = ({ to, intl }) => {
|
function Visibility({ to, intl }) {
|
||||||
const icon = to === 'private' ? faEyeSlash : faEye;
|
const icon = to === 'private' ? faEyeSlash : faEye;
|
||||||
const label = to === 'private'
|
const label = to === 'private'
|
||||||
? intl.formatMessage(messages['profile.visibility.who.just.me'])
|
? intl.formatMessage(messages['profile.visibility.who.just.me'])
|
||||||
@@ -18,7 +18,7 @@ const Visibility = ({ to, intl }) => {
|
|||||||
<FontAwesomeIcon icon={icon} /> {label}
|
<FontAwesomeIcon icon={icon} /> {label}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
Visibility.propTypes = {
|
Visibility.propTypes = {
|
||||||
to: PropTypes.oneOf(['private', 'all_users']),
|
to: PropTypes.oneOf(['private', 'all_users']),
|
||||||
@@ -30,7 +30,7 @@ Visibility.defaultProps = {
|
|||||||
to: 'private',
|
to: 'private',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VisibilitySelect = ({ intl, className, ...props }) => {
|
function VisibilitySelect({ intl, className, ...props }) {
|
||||||
const { value } = props;
|
const { value } = props;
|
||||||
const icon = value === 'private' ? faEyeSlash : faEye;
|
const icon = value === 'private' ? faEyeSlash : faEye;
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ const VisibilitySelect = ({ intl, className, ...props }) => {
|
|||||||
</select>
|
</select>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
VisibilitySelect.propTypes = {
|
VisibilitySelect.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ export { default as saga } from './data/sagas';
|
|||||||
export { default as ProfilePage } from './ProfilePage';
|
export { default as ProfilePage } from './ProfilePage';
|
||||||
export { default as NotFoundPage } from './NotFoundPage';
|
export { default as NotFoundPage } from './NotFoundPage';
|
||||||
export { default as messages } from './ProfilePage.messages';
|
export { default as messages } from './ProfilePage.messages';
|
||||||
export { default as ProfilePluginPage } from './ProfilePluginPage';
|
|
||||||
|
|||||||
@@ -162,28 +162,4 @@
|
|||||||
position: relative;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,6 +2,6 @@ import 'core-js/stable';
|
|||||||
import 'regenerator-runtime/runtime';
|
import 'regenerator-runtime/runtime';
|
||||||
|
|
||||||
import Enzyme from 'enzyme';
|
import Enzyme from 'enzyme';
|
||||||
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
import Adapter from 'enzyme-adapter-react-16';
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
Enzyme.configure({ adapter: new Adapter() });
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
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;
|
|
||||||
Reference in New Issue
Block a user