Compare commits
4 Commits
master
...
update-bro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f72fbebf3 | ||
|
|
15a0736729 | ||
|
|
e978055bc4 | ||
|
|
812d23f448 |
15
.env
15
.env
@@ -1,10 +1,12 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME=''
|
ACCESS_TOKEN_COOKIE_NAME=''
|
||||||
ACCOUNT_PROFILE_URL=''
|
|
||||||
BASE_URL=''
|
BASE_URL=''
|
||||||
|
COACHING_ENABLED=''
|
||||||
CREDENTIALS_BASE_URL=''
|
CREDENTIALS_BASE_URL=''
|
||||||
CSRF_TOKEN_API_PATH=''
|
CSRF_TOKEN_API_PATH=''
|
||||||
|
DEMOGRAPHICS_BASE_URL=''
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL=''
|
ECOMMERCE_BASE_URL=''
|
||||||
|
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||||
FAVICON_URL=''
|
FAVICON_URL=''
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME=''
|
LANGUAGE_PREFERENCE_COOKIE_NAME=''
|
||||||
LMS_BASE_URL=''
|
LMS_BASE_URL=''
|
||||||
@@ -12,11 +14,9 @@ LOGIN_URL=''
|
|||||||
LOGO_TRADEMARK_URL=''
|
LOGO_TRADEMARK_URL=''
|
||||||
LOGO_URL=''
|
LOGO_URL=''
|
||||||
LOGO_WHITE_URL=''
|
LOGO_WHITE_URL=''
|
||||||
SHOW_PUSH_CHANNEL=''
|
|
||||||
SHOW_EMAIL_CHANNEL=''
|
|
||||||
LOGOUT_URL=''
|
LOGOUT_URL=''
|
||||||
MARKETING_SITE_BASE_URL=''
|
MARKETING_SITE_BASE_URL=''
|
||||||
NODE_ENV='production'
|
NODE_ENV=''
|
||||||
ORDER_HISTORY_URL=''
|
ORDER_HISTORY_URL=''
|
||||||
PUBLISHER_BASE_URL=''
|
PUBLISHER_BASE_URL=''
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
||||||
@@ -26,14 +26,7 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL=''
|
SUPPORT_URL=''
|
||||||
USER_INFO_COOKIE_NAME=''
|
USER_INFO_COOKIE_NAME=''
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
ENABLE_ACCOUNT_DELETION=''
|
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
PASSWORD_RESET_SUPPORT_LINK=''
|
|
||||||
LEARNER_FEEDBACK_URL=''
|
|
||||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
|
||||||
# Fallback in local style files
|
|
||||||
PARAGON_THEME_URLS={}
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
|
||||||
BASE_URL='localhost:1997'
|
BASE_URL='localhost:1997'
|
||||||
|
COACHING_ENABLED=''
|
||||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
|
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
|
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
@@ -25,16 +27,8 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL='http://localhost:18000/support'
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
ENABLE_ACCOUNT_DELETION=''
|
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
SHOW_PUSH_CHANNEL='true'
|
|
||||||
SHOW_EMAIL_CHANNEL='true'
|
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
PASSWORD_RESET_SUPPORT_LINK='mailto:support@example.com'
|
|
||||||
LEARNER_FEEDBACK_URL=''
|
|
||||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
|
||||||
# Fallback in local style files
|
|
||||||
PARAGON_THEME_URLS={}
|
|
||||||
|
|||||||
10
.env.test
10
.env.test
@@ -1,9 +1,12 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
BASE_URL='localhost:1997'
|
BASE_URL='localhost:1997'
|
||||||
|
COACHING_ENABLED=''
|
||||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
|
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
|
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
@@ -23,14 +26,7 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL='http://localhost:18000/support'
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
ENABLE_ACCOUNT_DELETION=''
|
|
||||||
SHOW_PUSH_CHANNEL=''
|
|
||||||
SHOW_EMAIL_CHANNEL=''
|
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
LEARNER_FEEDBACK_URL=''
|
|
||||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
|
||||||
PARAGON_THEME_URLS={}
|
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ dist/
|
|||||||
node_modules/
|
node_modules/
|
||||||
__mocks__/
|
__mocks__/
|
||||||
__snapshots__/
|
__snapshots__/
|
||||||
src/i18n/messages/
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
const { createConfig } = require('@openedx/frontend-build');
|
const { createConfig } = require('@edx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('eslint');
|
module.exports = createConfig('eslint');
|
||||||
|
|||||||
24
.github/pull_request_template.md
vendored
24
.github/pull_request_template.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
### Description
|
|
||||||
|
|
||||||
Include a description of your changes here, along with a link to any relevant Jira tickets and/or GitHub issues.
|
|
||||||
|
|
||||||
#### How Has This Been Tested?
|
|
||||||
|
|
||||||
Please describe in detail how you tested your changes.
|
|
||||||
|
|
||||||
#### Screenshots/sandbox (optional):
|
|
||||||
Include a link to the sandbox for design changes or screenshot for before and after. **Remove this section if it's not applicable.**
|
|
||||||
|
|
||||||
|Before|After|
|
|
||||||
|-------|-----|
|
|
||||||
| | |
|
|
||||||
|
|
||||||
#### Merge Checklist
|
|
||||||
|
|
||||||
* [ ] If your update includes visual changes, have they been reviewed by a designer? Send them a link to the Sandbox, if applicable.
|
|
||||||
* [ ] Is there adequate test coverage for your changes?
|
|
||||||
|
|
||||||
#### Post-merge Checklist
|
|
||||||
|
|
||||||
* [ ] Deploy the changes to prod after verifying on stage or ask **@jacobo-dominguez-wgu** to do it.
|
|
||||||
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -9,21 +9,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
node: [16]
|
||||||
|
npm: [8.5.0]
|
||||||
npm-test:
|
npm-test:
|
||||||
- i18n_extract
|
- i18n_extract
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm install -g npm@${{ matrix.npm }}
|
||||||
- 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 }}
|
||||||
- name: Coverage
|
- name: upload coverage
|
||||||
if: matrix.npm-test == 'test'
|
uses: codecov/codecov-action@v3
|
||||||
uses: codecov/codecov-action@v4
|
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
fail_ci_if_error: false
|
||||||
fail_ci_if_error: true
|
|
||||||
|
|||||||
3
.github/workflows/lockfileversion-check.yml
vendored
3
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,5 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version-check:
|
version-check:
|
||||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.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
|
|
||||||
44
.github/workflows/update-browserlist.yml
vendored
Normal file
44
.github/workflows/update-browserlist.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
name: Update Browserlist DB
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-dep:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node: [16]
|
||||||
|
npm: [8.5.x]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
- run: npm install -g npm@${{ matrix.npm }}
|
||||||
|
- run: make requirements
|
||||||
|
- name: Update dependencies
|
||||||
|
run: npx update-browserslist-db@latest
|
||||||
|
- name: Create Pull Request
|
||||||
|
id: cpr
|
||||||
|
uses: peter-evans/create-pull-request@v4.1.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.requirements_bot_github_token }}
|
||||||
|
commit-message: 'chore: update browserslist'
|
||||||
|
title: Update browserslist
|
||||||
|
body: |
|
||||||
|
- Dependency updates
|
||||||
|
|
||||||
|
Auto-generated by [create-pull-request][1]
|
||||||
|
|
||||||
|
[1]: https://github.com/peter-evans/create-pull-request
|
||||||
|
branch: update-browserslist
|
||||||
|
|
||||||
|
- name: Enable Pull Request Automerge
|
||||||
|
if: steps.cpr.outputs.pull-request-operation == 'created'
|
||||||
|
uses: peter-evans/enable-pull-request-automerge@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.requirements_bot_github_token }}
|
||||||
|
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
|
||||||
|
merge-method: squash
|
||||||
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 }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,4 +17,3 @@ temp/babel-plugin-react-intl
|
|||||||
/temp
|
/temp
|
||||||
/.vscode
|
/.vscode
|
||||||
/module.config.js
|
/module.config.js
|
||||||
src/i18n/messages/
|
|
||||||
9
.tx/config
Normal file
9
.tx/config
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
|
||||||
|
[o:open-edx:p:edx-platform:r:frontend-app-account]
|
||||||
|
file_filter = src/i18n/messages/<lang>.json
|
||||||
|
source_file = src/i18n/transifex_input.json
|
||||||
|
source_lang = en
|
||||||
|
type = KEYVALUEJSON
|
||||||
|
|
||||||
38
Makefile
38
Makefile
@@ -1,10 +1,15 @@
|
|||||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
export TRANSIFEX_RESOURCE = frontend-app-account
|
||||||
|
transifex_resource = frontend-app-account
|
||||||
|
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -17,11 +22,6 @@ test.npm.%: validate-no-uncommitted-package-lock-changes
|
|||||||
npm run $(*)
|
npm run $(*)
|
||||||
|
|
||||||
.PHONY: requirements
|
.PHONY: requirements
|
||||||
|
|
||||||
precommit:
|
|
||||||
npm run lint
|
|
||||||
npm audit
|
|
||||||
|
|
||||||
requirements: ## install ci requirements
|
requirements: ## install ci requirements
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
@@ -41,18 +41,20 @@ detect_changed_source_translations:
|
|||||||
# Checking for changed translations...
|
# Checking for changed translations...
|
||||||
git diff --exit-code $(i18n)
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
pull_translations:
|
# Pushes translations to Transifex. You must run make extract_translations first.
|
||||||
rm -rf src/i18n/messages
|
push_translations:
|
||||||
mkdir src/i18n/messages
|
# Pushing strings to Transifex...
|
||||||
cd src/i18n/messages \
|
tx push -s
|
||||||
&& atlas pull $(ATLAS_OPTIONS) \
|
# Fetching hashes from Transifex...
|
||||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||||
translations/paragon/src/i18n/messages:paragon \
|
# Writing out comments to file...
|
||||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||||
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
# Pushing comments to Transifex...
|
||||||
translations/frontend-app-account/src/i18n/messages:frontend-app-account
|
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||||
|
|
||||||
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-app-account
|
# Pulls translations from Transifex.
|
||||||
|
pull_translations:
|
||||||
|
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
||||||
|
|
||||||
# This target is used by Travis.
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
206
README.rst
206
README.rst
@@ -1,13 +1,12 @@
|
|||||||
####################
|
|
||||||
frontend-app-account
|
|
||||||
####################
|
|
||||||
|
|
||||||
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||||
|
|
||||||
|
frontend-app-account
|
||||||
|
====================
|
||||||
|
|
||||||
********
|
Please tag **@edx/community-engineering** on any PRs or issues. Thanks!
|
||||||
Purpose
|
|
||||||
********
|
Introduction
|
||||||
|
------------
|
||||||
|
|
||||||
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
||||||
|
|
||||||
@@ -16,33 +15,37 @@ What is the domain of this MFE?
|
|||||||
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
|
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
|
||||||
|
|
||||||
- Account settings page
|
- Account settings page
|
||||||
|
|
||||||
|
- Demographics collection
|
||||||
|
|
||||||
- IDV (Identity Verification)
|
- IDV (Identity Verification)
|
||||||
|
|
||||||
***************
|
Installation
|
||||||
Getting Started
|
------------
|
||||||
***************
|
|
||||||
|
|
||||||
Prerequisites
|
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
|
||||||
=============
|
|
||||||
|
|
||||||
`Tutor`_ is currently recommended as a development environment for your
|
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
|
||||||
new MFE. Please refer
|
|
||||||
to the `relevant tutor-mfe documentation`_ to get started using it.
|
|
||||||
|
|
||||||
.. _Tutor: https://github.com/overhangio/tutor
|
2. Start up Devstack, if it's not already started.
|
||||||
|
|
||||||
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
|
3. Log in to Devstack (http://localhost:18000/login )
|
||||||
|
|
||||||
Plugins
|
4. Within this project, install requirements and start the development server:
|
||||||
=======
|
|
||||||
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
|
||||||
|
|
||||||
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
.. code-block::
|
||||||
|
|
||||||
|
npm install
|
||||||
|
npm start # The server will run on port 1997
|
||||||
|
|
||||||
|
5. Once the dev server is up, visit http://localhost:1997 to access the MFE
|
||||||
|
|
||||||
|
.. image:: ./docs/images/localhost_preview.png
|
||||||
|
|
||||||
Environment Variables/Setup Notes
|
Environment Variables/Setup Notes
|
||||||
=================================
|
---------------------------------
|
||||||
|
|
||||||
This MFE is configured via the ``frontend-platform`` configuration module. For more information on MFE configuration see the `Configuration documentation`_.
|
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||||
|
|
||||||
The account settings micro-frontend also supports the following additional variable:
|
The account settings micro-frontend also supports the following additional variable:
|
||||||
|
|
||||||
@@ -52,22 +55,28 @@ Example: ``https://support.example.com``
|
|||||||
|
|
||||||
The fully-qualified URL to the support page in the target environment.
|
The fully-qualified URL to the support page in the target environment.
|
||||||
|
|
||||||
``PASSWORD_RESET_SUPPORT_LINK``
|
edX-specific Environment Variables
|
||||||
|
**********************************
|
||||||
|
|
||||||
Examples:
|
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and are unsupported in Open edX. Enabling these environment variables will result in undefined behavior in Open edX installations:
|
||||||
|
|
||||||
- ``https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-``
|
``COACHING_ENABLED``
|
||||||
|
|
||||||
- ``mailto:support@example.com``
|
Example: ``true`` | ``''`` (empty strings are falsy)
|
||||||
|
|
||||||
The fully-qualified URL to the support page or email to request the support from in the target environment.
|
Enables support for a section of the micro-frontend that helps users arrange for coaching sessions. Integrates with a private coaching plugin and is only used by edx.org.
|
||||||
|
|
||||||
``ENABLE_ACCOUNT_DELETION``
|
``ENABLE_DEMOGRAPHICS_COLLECTION``
|
||||||
|
|
||||||
Example: ``'false'`` | ``''`` (empty strings are true)
|
Example: ``true`` | ``''`` (empty strings are falsy)
|
||||||
|
|
||||||
Enable the account deletion option, defaults to true.
|
Enables support for a section of the account settings page where a user can enter demographics information. Integrates with a private demographics service and is only used by edx.org.
|
||||||
To disable account deletion set ``ENABLE_ACCOUNT_DELETION`` to ``'false'`` (string), otherwise it will default to true.
|
|
||||||
|
``DEMOGRAPHICS_BASE_URL``
|
||||||
|
|
||||||
|
Example: ``https://demographics.example.com``
|
||||||
|
|
||||||
|
Required only if ``ENABLE_DEMOGRAPHICS_COLLECTION`` is true. The fully-qualified URL to the private demographics service in the target environment.
|
||||||
|
|
||||||
Example build syntax with a single environment variable:
|
Example build syntax with a single environment variable:
|
||||||
|
|
||||||
@@ -75,142 +84,21 @@ Example build syntax with a single environment variable:
|
|||||||
|
|
||||||
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
||||||
|
|
||||||
For more information see the document: `Configuration documentation`_
|
For more information see the document: `Micro-frontend applications in Open
|
||||||
|
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||||
.. _Configuration documentation: https://openedx.github.io/frontend-platform/module-Config.html
|
|
||||||
|
|
||||||
Cloning and Startup
|
|
||||||
===================
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
|
|
||||||
|
|
||||||
1. Clone your new repo:
|
|
||||||
|
|
||||||
``git clone https://github.com/openedx/frontend-app-account.git``
|
|
||||||
|
|
||||||
2. Use the version of Node specified in the ``.nvmrc`` file.
|
|
||||||
|
|
||||||
The current version of the micro-frontend build scripts supports the version of Node found in ``.nvmrc``.
|
|
||||||
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-account && npm ci``
|
|
||||||
|
|
||||||
4. Start the dev server:
|
|
||||||
|
|
||||||
``npm start``
|
|
||||||
|
|
||||||
Or for local development with custom configuration:
|
|
||||||
|
|
||||||
``npm run dev``
|
|
||||||
|
|
||||||
This runs the dev server with PUBLIC_PATH=/account/, MFE_CONFIG_API_URL pointing to localhost:8000, and hosts on apps.local.openedx.io.
|
|
||||||
|
|
||||||
Local module development
|
|
||||||
=========================
|
|
||||||
|
|
||||||
To develop locally on modules that are installed into this app, you'll need to create a ``module.config.js``
|
|
||||||
file (which is git-ignored) that defines where to find your local modules, for instance:
|
|
||||||
|
|
||||||
.. code-block:: js
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/*
|
|
||||||
Modules you want to use from local source code. Adding a module here means that when this app
|
|
||||||
runs its build, it'll resolve the source from peer directories of this app.
|
|
||||||
|
|
||||||
moduleName: the name you use to import code from the module.
|
|
||||||
dir: The relative path to the module's source code.
|
|
||||||
dist: The sub-directory of the source code where it puts its build artifact. Often "dist", though you
|
|
||||||
may want to use "src" if the module installs React as a peer/dev dependency.
|
|
||||||
*/
|
|
||||||
localModules: [
|
|
||||||
{ moduleName: '@openedx/paragon/scss', dir: '../paragon', dist: 'scss' },
|
|
||||||
{ moduleName: '@openedx/paragon', dir: '../paragon', dist: 'dist' },
|
|
||||||
{ moduleName: '@openedx/frontend-enterprise', dir: '../frontend-enterprise', dist: 'src' },
|
|
||||||
{ moduleName: '@openedx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
See https://github.com/openedx/frontend-build#local-module-configuration-for-webpack for more details.
|
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
===========
|
------------
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
||||||
Development Roadmap
|
Development Roadmap
|
||||||
===================
|
-------------------
|
||||||
|
|
||||||
We don't have anything planned for the core of the MFE (the account settings page) - this MFE is currently in maintenance mode.
|
We don't have anything planned for the core of the MFE (the account settings page) - this MFE is currently in maintenance mode.
|
||||||
There may be a replacement for IDV coming down the pipe, so that may be DEPRed.
|
There may be a replacement for IDV coming down the pipe, so that may be DEPRed.
|
||||||
|
In the future, it's possible that demographics could be modeled as a plugin rather than being hard-coded into this MFE.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
Getting Help
|
|
||||||
===========
|
|
||||||
|
|
||||||
If you're having trouble, we have discussion forums at
|
|
||||||
https://discuss.openedx.org where you can connect with others in the community.
|
|
||||||
|
|
||||||
Our real-time conversations are on Slack. You can request a `Slack
|
|
||||||
invitation`_, then join our `community Slack workspace`_. Because this is a
|
|
||||||
frontend repository, the best place to discuss it would be in the `#wg-frontend
|
|
||||||
channel`_.
|
|
||||||
|
|
||||||
For anything non-trivial, the best path is to open an issue in this repository
|
|
||||||
with as many details about the issue you are facing as you can provide.
|
|
||||||
|
|
||||||
https://github.com/openedx/frontend-app-account/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/community/connect
|
|
||||||
|
|
||||||
|
|
||||||
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 or from inspecting catalog-info.yaml.
|
|
||||||
|
|
||||||
Reporting Security Issues
|
|
||||||
=========================
|
|
||||||
|
|
||||||
Please do not report security issues in public. Please email security@openedx.org.
|
|
||||||
|
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
@@ -218,7 +106,7 @@ Please do not report security issues in public. Please email security@openedx.or
|
|||||||
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
||||||
:alt: Continuous Integration
|
:alt: Continuous Integration
|
||||||
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
|
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
|
||||||
:target: https://codecov.io/gh/openedx/frontend-app-account/
|
:target: https://codecov.io/gh/edx/frontend-app-account
|
||||||
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-account.svg
|
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-account.svg
|
||||||
:target: @edx/frontend-app-account
|
:target: @edx/frontend-app-account
|
||||||
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-account.svg
|
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-account.svg
|
||||||
|
|||||||
@@ -1,19 +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: 'frontend-app-account'
|
|
||||||
description: "Open edX micro-frontend application for managing user account information."
|
|
||||||
links:
|
|
||||||
- url: "https://github.com/openedx/frontend-app-account"
|
|
||||||
title: "Frontend app account"
|
|
||||||
icon: "Web"
|
|
||||||
annotations:
|
|
||||||
openedx.org/arch-interest-groups: ""
|
|
||||||
openedx.org/release: "master"
|
|
||||||
spec:
|
|
||||||
owner: jacobo-dominguez-wgu
|
|
||||||
type: 'website'
|
|
||||||
lifecycle: 'production'
|
|
||||||
15
codecov.yml
15
codecov.yml
@@ -1,15 +0,0 @@
|
|||||||
coverage:
|
|
||||||
status:
|
|
||||||
project:
|
|
||||||
default:
|
|
||||||
enabled: yes
|
|
||||||
target: auto
|
|
||||||
threshold: 0%
|
|
||||||
patch:
|
|
||||||
default:
|
|
||||||
enabled: yes
|
|
||||||
target: auto
|
|
||||||
threshold: 0%
|
|
||||||
ignore:
|
|
||||||
- "src/i18n"
|
|
||||||
- "src/index.jsx"
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const { createConfig } = require('@openedx/frontend-build');
|
const { createConfig } = require('@edx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('jest', {
|
module.exports = createConfig('jest', {
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
|
|||||||
6
openedx.yaml
Normal file
6
openedx.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# This file describes this Open edX repo, as described in OEP-2:
|
||||||
|
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0002-bp-repo-metadata.html#specification
|
||||||
|
|
||||||
|
nick: acct
|
||||||
|
oeps: {}
|
||||||
|
openedx-release: {ref: master}
|
||||||
43655
package-lock.json
generated
43655
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
86
package.json
86
package.json
@@ -10,10 +10,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"build": "fedx-scripts webpack",
|
||||||
"dev": "PUBLIC_PATH=/account/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
||||||
"i18n_extract": "fedx-scripts formatjs extract",
|
|
||||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
|
||||||
"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"
|
||||||
@@ -29,27 +27,26 @@
|
|||||||
"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": "^14.6.0",
|
"@edx/frontend-component-footer": "11.5.2",
|
||||||
"@edx/frontend-component-header": "^6.2.0",
|
"@edx/frontend-component-header": "3.5.0",
|
||||||
"@edx/frontend-platform": "^8.4.0",
|
"@edx/frontend-platform": "2.6.2",
|
||||||
"@edx/openedx-atlas": "^0.7.0",
|
"@edx/paragon": "20.21.2",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.6.0",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "0.2.6",
|
"@fortawesome/react-fontawesome": "0.2.0",
|
||||||
"@openedx/frontend-plugin-framework": "^1.7.0",
|
"@tensorflow-models/blazeface": "0.0.7",
|
||||||
"@openedx/paragon": "^23.4.5",
|
"@tensorflow/tfjs-converter": "3.21.0",
|
||||||
"@tensorflow-models/blazeface": "0.1.0",
|
"@tensorflow/tfjs-core": "3.21.0",
|
||||||
"@tensorflow/tfjs-converter": "4.22.0",
|
"bowser": "2.11.0",
|
||||||
"@tensorflow/tfjs-core": "4.22.0",
|
"classnames": "2.3.2",
|
||||||
"bowser": "2.14.1",
|
"core-js": "3.26.1",
|
||||||
"classnames": "2.5.1",
|
|
||||||
"core-js": "3.48.0",
|
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"form-urlencoded": "6.1.6",
|
"form-urlencoded": "4.5.1",
|
||||||
"formdata-polyfill": "4.0.10",
|
"formdata-polyfill": "4.0.10",
|
||||||
|
"history": "4.10.1",
|
||||||
"jslib-html5-camera-photo": "3.3.4",
|
"jslib-html5-camera-photo": "3.3.4",
|
||||||
"lodash.camelcase": "4.3.0",
|
"lodash.camelcase": "4.3.0",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
@@ -60,35 +57,38 @@
|
|||||||
"lodash.omit": "4.5.0",
|
"lodash.omit": "4.5.0",
|
||||||
"lodash.pick": "4.4.0",
|
"lodash.pick": "4.4.0",
|
||||||
"lodash.pickby": "4.6.0",
|
"lodash.pickby": "4.6.0",
|
||||||
"lodash.snakecase": "4.1.1",
|
"long": "5.2.1",
|
||||||
"long": "5.3.2",
|
"memoize-one": "5.2.1",
|
||||||
"memoize-one": "^6.0.0",
|
"prop-types": "15.7.2",
|
||||||
"prop-types": "15.8.1",
|
"qs": "6.10.3",
|
||||||
"qs": "6.15.0",
|
"react": "16.14.0",
|
||||||
"react": "18.3.1",
|
"react-dom": "16.14.0",
|
||||||
"react-dom": "18.3.1",
|
|
||||||
"react-helmet": "6.1.0",
|
"react-helmet": "6.1.0",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.2.9",
|
||||||
"react-router": "^6.25.1",
|
"react-router": "5.2.1",
|
||||||
"react-router-dom": "^6.25.1",
|
"react-router-dom": "5.3.0",
|
||||||
"react-router-hash-link": "2.4.3",
|
"react-router-hash-link": "2.4.3",
|
||||||
"react-scrollspy": "3.4.3",
|
"react-scrollspy": "3.4.3",
|
||||||
"react-transition-group": "4.4.5",
|
"react-transition-group": "4.4.5",
|
||||||
"redux": "4.2.1",
|
"redux": "4.1.2",
|
||||||
"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.4.2",
|
"redux-saga": "1.1.3",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.3.0",
|
||||||
"regenerator-runtime": "0.14.1",
|
"regenerator-runtime": "0.13.11",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "4.0.0",
|
||||||
"universal-cookie": "7.2.2"
|
"universal-cookie": "4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "1.5.1",
|
"@edx/browserslist-config": "1.1.1",
|
||||||
"@openedx/frontend-build": "^14.6.2",
|
"@edx/frontend-build": "12.4.0",
|
||||||
"@testing-library/jest-dom": "6.9.1",
|
"@edx/reactifex": "1.1.0",
|
||||||
"@testing-library/react": "14.3.1",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
"react-test-renderer": "^18.3.1",
|
"@testing-library/react": "12.1.5",
|
||||||
"redux-mock-store": "1.5.5"
|
"enzyme": "3.11.0",
|
||||||
|
"enzyme-adapter-react-16": "1.15.7",
|
||||||
|
"react-test-renderer": "16.14.0",
|
||||||
|
"reactifex": "1.1.1",
|
||||||
|
"redux-mock-store": "1.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,148 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en-us">
|
<html lang="en-us">
|
||||||
<head>
|
<head>
|
||||||
<title>Account | <%= process.env.SITE_NAME %></title>
|
<title>Account | <%= process.env.SITE_NAME %></title>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link
|
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||||
rel="shortcut icon"
|
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||||
href="<%=htmlWebpackPlugin.options.FAVICON_URL%>"
|
<script
|
||||||
type="image/x-icon"
|
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||||
/>
|
></script>
|
||||||
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
<% } %>
|
||||||
<script src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"></script>
|
|
||||||
<% } %>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- begin usabilla live embed code -->
|
|
||||||
<script defer type="text/javascript">
|
|
||||||
window.lightningjs ||
|
|
||||||
(function (n) {
|
|
||||||
var e = "lightningjs";
|
|
||||||
function t(e, t) {
|
|
||||||
var r, i, a, o, d, c;
|
|
||||||
return (
|
|
||||||
t && (t += (/\?/.test(t) ? "&" : "?") + "lv=1"),
|
|
||||||
n[e] ||
|
|
||||||
((r = window),
|
|
||||||
(i = document),
|
|
||||||
(a = e),
|
|
||||||
(o = i.location.protocol),
|
|
||||||
(d = "load"),
|
|
||||||
(c = 0),
|
|
||||||
(function () {
|
|
||||||
n[a] = function () {
|
|
||||||
var t = arguments,
|
|
||||||
i = this,
|
|
||||||
o = ++c,
|
|
||||||
d = (i && i != r && i.id) || 0;
|
|
||||||
function s() {
|
|
||||||
return (s.id = o), n[a].apply(s, arguments);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
(e.s = e.s || []).push([o, d, t]),
|
|
||||||
(s.then = function (n, t, r) {
|
|
||||||
var i = (e.fh[o] = e.fh[o] || []),
|
|
||||||
a = (e.eh[o] = e.eh[o] || []),
|
|
||||||
d = (e.ph[o] = e.ph[o] || []);
|
|
||||||
return (
|
|
||||||
n && i.push(n), t && a.push(t), r && d.push(r), s
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
s
|
|
||||||
);
|
|
||||||
};
|
|
||||||
var e = (n[a]._ = {});
|
|
||||||
function s() {
|
|
||||||
e.P(d), (e.w = 1), n[a]("_load");
|
|
||||||
}
|
|
||||||
(e.fh = {}),
|
|
||||||
(e.eh = {}),
|
|
||||||
(e.ph = {}),
|
|
||||||
(e.l = t
|
|
||||||
? t.replace(/^\/\//, ("https:" == o ? o : "http:") + "//")
|
|
||||||
: t),
|
|
||||||
(e.p = { 0: +new Date() }),
|
|
||||||
(e.P = function (n) {
|
|
||||||
e.p[n] = new Date() - e.p[0];
|
|
||||||
}),
|
|
||||||
e.w && s(),
|
|
||||||
r.addEventListener
|
|
||||||
? r.addEventListener(d, s, !1)
|
|
||||||
: r.attachEvent("onload", s);
|
|
||||||
var l = function () {
|
|
||||||
function n() {
|
|
||||||
return [
|
|
||||||
"<!DOCTYPE ",
|
|
||||||
o,
|
|
||||||
"><",
|
|
||||||
o,
|
|
||||||
"><head></head><",
|
|
||||||
t,
|
|
||||||
"><",
|
|
||||||
r,
|
|
||||||
' src="',
|
|
||||||
e.l,
|
|
||||||
'"></',
|
|
||||||
r,
|
|
||||||
"></",
|
|
||||||
t,
|
|
||||||
"></",
|
|
||||||
o,
|
|
||||||
">",
|
|
||||||
].join("");
|
|
||||||
}
|
|
||||||
var t = "body",
|
|
||||||
r = "script",
|
|
||||||
o = "html",
|
|
||||||
d = i[t];
|
|
||||||
if (!d) return setTimeout(l, 100);
|
|
||||||
e.P(1);
|
|
||||||
var c,
|
|
||||||
s = i.createElement("div"),
|
|
||||||
h = s.appendChild(i.createElement("div")),
|
|
||||||
u = i.createElement("iframe");
|
|
||||||
(s.style.display = "none"),
|
|
||||||
(d.insertBefore(s, d.firstChild).id = "lightningjs-" + a),
|
|
||||||
(u.frameBorder = "0"),
|
|
||||||
(u.id = "lightningjs-frame-" + a),
|
|
||||||
/MSIE[ ]+6/.test(navigator.userAgent) &&
|
|
||||||
(u.src = "javascript:false"),
|
|
||||||
(u.allowTransparency = "true"),
|
|
||||||
h.appendChild(u);
|
|
||||||
try {
|
|
||||||
u.contentWindow.document.open();
|
|
||||||
} catch (n) {
|
|
||||||
(e.domain = i.domain),
|
|
||||||
(c =
|
|
||||||
"javascript:var d=document.open();d.domain='" +
|
|
||||||
i.domain +
|
|
||||||
"';"),
|
|
||||||
(u.src = c + "void(0);");
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var p = u.contentWindow.document;
|
|
||||||
p.write(n()), p.close();
|
|
||||||
} catch (e) {
|
|
||||||
u.src =
|
|
||||||
c +
|
|
||||||
'd.write("' +
|
|
||||||
n().replace(/"/g, String.fromCharCode(92) + '"') +
|
|
||||||
'");d.close();';
|
|
||||||
}
|
|
||||||
e.P(2);
|
|
||||||
};
|
|
||||||
e.l && l();
|
|
||||||
})()),
|
|
||||||
(n[e].lv = "1"),
|
|
||||||
n[e]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var r = (window.lightningjs = t(e));
|
|
||||||
(r.require = t), (r.modules = n);
|
|
||||||
})({});
|
|
||||||
</script>
|
|
||||||
<!-- end usabilla live embed code -->
|
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackagePatterns": ["@edx", "@openedx"],
|
"matchPackagePatterns": ["@edx"],
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"automerge": true
|
"automerge": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppContext } from '@edx/frontend-platform/react';
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
|
import { getConfig, history, getQueryParameters } from '@edx/frontend-platform';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
getLanguageList,
|
getLanguageList,
|
||||||
} from '@edx/frontend-platform/i18n';
|
} from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Container, Hyperlink, Icon, Alert,
|
Button, Hyperlink, Icon, Alert,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { CheckCircle, Error, WarningFilled } from '@openedx/paragon/icons';
|
import { CheckCircle, Error, WarningFilled } from '@edx/paragon/icons';
|
||||||
|
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
import {
|
import {
|
||||||
@@ -45,21 +45,24 @@ import {
|
|||||||
GENDER_OPTIONS,
|
GENDER_OPTIONS,
|
||||||
COUNTRY_WITH_STATES,
|
COUNTRY_WITH_STATES,
|
||||||
COPPA_COMPLIANCE_YEAR,
|
COPPA_COMPLIANCE_YEAR,
|
||||||
WORK_EXPERIENCE_OPTIONS,
|
|
||||||
getStatesList,
|
getStatesList,
|
||||||
FIELD_LABELS,
|
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
import { fetchSiteLanguages } from './site-language';
|
import { fetchSiteLanguages } from './site-language';
|
||||||
import { fetchNotificationPreferences } from '../notification-preferences/data/thunks';
|
import CoachingToggle from './coaching/CoachingToggle';
|
||||||
import NotificationSettings from '../notification-preferences/NotificationSettings';
|
import DemographicsSection from './demographics/DemographicsSection';
|
||||||
import { withLocation, withNavigate } from './hoc';
|
|
||||||
import AdditionalProfileFieldsSlot from '../plugin-slots/AdditionalProfileFieldsSlot';
|
|
||||||
|
|
||||||
class AccountSettingsPage extends React.Component {
|
class AccountSettingsPage extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
// If there is a "duplicate_provider" query parameter, that's the backend's
|
||||||
|
// way of telling us that the provider account the user tried to link is already linked
|
||||||
|
// to another user account on the platform. We use this to display a message to that effect,
|
||||||
|
// and remove the parameter from the URL.
|
||||||
const duplicateTpaProvider = getQueryParameters().duplicate_provider;
|
const duplicateTpaProvider = getQueryParameters().duplicate_provider;
|
||||||
|
if (duplicateTpaProvider !== undefined) {
|
||||||
|
history.replace(history.location.pathname);
|
||||||
|
}
|
||||||
this.state = {
|
this.state = {
|
||||||
duplicateTpaProvider,
|
duplicateTpaProvider,
|
||||||
};
|
};
|
||||||
@@ -67,8 +70,8 @@ class AccountSettingsPage extends React.Component {
|
|||||||
this.navLinkRefs = {
|
this.navLinkRefs = {
|
||||||
'#basic-information': React.createRef(),
|
'#basic-information': React.createRef(),
|
||||||
'#profile-information': React.createRef(),
|
'#profile-information': React.createRef(),
|
||||||
|
'#demographics-information': React.createRef(),
|
||||||
'#social-media': React.createRef(),
|
'#social-media': React.createRef(),
|
||||||
'#notifications': React.createRef(),
|
|
||||||
'#site-preferences': React.createRef(),
|
'#site-preferences': React.createRef(),
|
||||||
'#linked-accounts': React.createRef(),
|
'#linked-accounts': React.createRef(),
|
||||||
'#delete-account': React.createRef(),
|
'#delete-account': React.createRef(),
|
||||||
@@ -76,9 +79,8 @@ class AccountSettingsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchNotificationPreferences();
|
|
||||||
this.props.fetchSettings();
|
this.props.fetchSettings();
|
||||||
this.props.fetchSiteLanguages(this.props.navigate);
|
this.props.fetchSiteLanguages();
|
||||||
sendTrackingLogEvent('edx.user.settings.viewed', {
|
sendTrackingLogEvent('edx.user.settings.viewed', {
|
||||||
page: 'account',
|
page: 'account',
|
||||||
visibility: null,
|
visibility: null,
|
||||||
@@ -124,15 +126,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
countryOptions: [{
|
countryOptions: [{
|
||||||
value: '',
|
value: '',
|
||||||
label: this.props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
|
label: this.props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
|
||||||
}].concat(
|
}].concat(getCountryList(locale).map(({ code, name }) => ({ value: code, label: name }))),
|
||||||
this.removeDisabledCountries(
|
|
||||||
getCountryList(locale).map(({ code, name }) => ({
|
|
||||||
value: code,
|
|
||||||
label: name,
|
|
||||||
disabled: this.isDisabledCountry(code),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
stateOptions: [{
|
stateOptions: [{
|
||||||
value: '',
|
value: '',
|
||||||
label: this.props.intl.formatMessage(messages['account.settings.field.state.options.empty']),
|
label: this.props.intl.formatMessage(messages['account.settings.field.state.options.empty']),
|
||||||
@@ -153,47 +147,14 @@ class AccountSettingsPage extends React.Component {
|
|||||||
value: key,
|
value: key,
|
||||||
label: this.props.intl.formatMessage(messages[`account.settings.field.gender.options.${key || 'empty'}`]),
|
label: this.props.intl.formatMessage(messages[`account.settings.field.gender.options.${key || 'empty'}`]),
|
||||||
})),
|
})),
|
||||||
workExperienceOptions: WORK_EXPERIENCE_OPTIONS.map(key => ({
|
|
||||||
value: key,
|
|
||||||
label: key === '' ? this.props.intl.formatMessage(messages['account.settings.field.work.experience.options.empty']) : key,
|
|
||||||
})),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
canDeleteAccount = () => {
|
|
||||||
const { committedValues } = this.props;
|
|
||||||
return !getConfig().COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED.includes(committedValues.country);
|
|
||||||
};
|
|
||||||
|
|
||||||
removeDisabledCountries = (countryList) => {
|
|
||||||
const { countriesCodesList, committedValues } = this.props;
|
|
||||||
const committedCountry = committedValues?.country;
|
|
||||||
|
|
||||||
if (!countriesCodesList.length) {
|
|
||||||
return countryList;
|
|
||||||
}
|
|
||||||
return countryList.filter(({ value }) => value === committedCountry || countriesCodesList.find(x => x === value));
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEditableFieldChange = (name, value) => {
|
handleEditableFieldChange = (name, value) => {
|
||||||
this.props.updateDraft(name, value);
|
this.props.updateDraft(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (formId, values) => {
|
handleSubmit = (formId, values) => {
|
||||||
if (formId === FIELD_LABELS.COUNTRY && this.isDisabledCountry(values)) {
|
this.props.saveSettings(formId, values);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { formValues } = this.props;
|
|
||||||
let extendedProfileObject = {};
|
|
||||||
|
|
||||||
if ('extended_profile' in formValues && formValues.extended_profile.some((field) => field.field_name === formId)) {
|
|
||||||
extendedProfileObject = {
|
|
||||||
extended_profile: formValues.extended_profile.map(field => (field.field_name === formId
|
|
||||||
? { ...field, field_value: values }
|
|
||||||
: field)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.props.saveSettings(formId, values, extendedProfileObject);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmitProfileName = (formId, values) => {
|
handleSubmitProfileName = (formId, values) => {
|
||||||
@@ -224,12 +185,6 @@ class AccountSettingsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
isDisabledCountry = (country) => {
|
|
||||||
const { countriesCodesList } = this.props;
|
|
||||||
|
|
||||||
return countriesCodesList.length > 0 && !countriesCodesList.find(x => x === country);
|
|
||||||
};
|
|
||||||
|
|
||||||
isEditable(fieldName) {
|
isEditable(fieldName) {
|
||||||
return !this.props.staticFields.includes(fieldName);
|
return !this.props.staticFields.includes(fieldName);
|
||||||
}
|
}
|
||||||
@@ -245,12 +200,6 @@ class AccountSettingsPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a "duplicate_provider" query parameter, that's the backend's
|
|
||||||
// way of telling us that the provider account the user tried to link is already linked
|
|
||||||
// to another user account on the platform. We use this to display a message to that effect,
|
|
||||||
// and remove the parameter from the URL.
|
|
||||||
this.props.navigate(this.props.location, { replace: true });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Alert variant="danger">
|
<Alert variant="danger">
|
||||||
@@ -348,9 +297,19 @@ class AccountSettingsPage extends React.Component {
|
|||||||
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.header'])}
|
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.header'])}
|
||||||
body={
|
body={
|
||||||
(
|
(
|
||||||
<div className="d-flex flex-row">
|
<>
|
||||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message'])}
|
<div className="d-flex flex-row">
|
||||||
</div>
|
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message'])}
|
||||||
|
</div>
|
||||||
|
<div className="d-flex flex-row-reverse mt-3">
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
href="https://support.edx.org/hc/en-us/articles/360004381594-Why-was-my-ID-verification-denied"
|
||||||
|
>
|
||||||
|
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.help.link'])}
|
||||||
|
</Button>{' '}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -485,6 +444,16 @@ class AccountSettingsPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderDemographicsSection() {
|
||||||
|
// check the result of an LMS API call to determine if we should render the DemographicsSection component
|
||||||
|
if (this.props.formValues.shouldDisplayDemographicsSection) {
|
||||||
|
return (
|
||||||
|
<DemographicsSection forwardRef={this.navLinkRefs['#demographics-information']} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const editableFieldProps = {
|
const editableFieldProps = {
|
||||||
onChange: this.handleEditableFieldChange,
|
onChange: this.handleEditableFieldChange,
|
||||||
@@ -499,15 +468,12 @@ class AccountSettingsPage extends React.Component {
|
|||||||
yearOfBirthOptions,
|
yearOfBirthOptions,
|
||||||
educationLevelOptions,
|
educationLevelOptions,
|
||||||
genderOptions,
|
genderOptions,
|
||||||
workExperienceOptions,
|
|
||||||
} = this.getLocalizedOptions(this.context.locale, this.props.formValues.country);
|
} = this.getLocalizedOptions(this.context.locale, this.props.formValues.country);
|
||||||
|
|
||||||
// Show State field only if the country is US (could include Canada later)
|
// Show State field only if the country is US (could include Canada later)
|
||||||
const { country } = this.props.formValues;
|
const showState = this.props.formValues.country === COUNTRY_WITH_STATES;
|
||||||
const showState = country === COUNTRY_WITH_STATES && !this.isDisabledCountry(country);
|
|
||||||
const { verifiedName } = this.props;
|
|
||||||
|
|
||||||
const hasWorkExperience = !!this.props.formValues?.extended_profile?.find(field => field.field_name === 'work_experience');
|
const { verifiedName } = this.props;
|
||||||
|
|
||||||
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
||||||
this.props.timeZoneOptions,
|
this.props.timeZoneOptions,
|
||||||
@@ -712,18 +678,6 @@ class AccountSettingsPage extends React.Component {
|
|||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.gender.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.gender.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
{hasWorkExperience
|
|
||||||
&& (
|
|
||||||
<EditableSelectField
|
|
||||||
name="work_experience"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues?.extended_profile?.find(field => field.field_name === 'work_experience')?.field_value}
|
|
||||||
options={workExperienceOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.work.experience'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.work.experience.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<EditableSelectField
|
<EditableSelectField
|
||||||
name="language_proficiencies"
|
name="language_proficiencies"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -733,10 +687,18 @@ class AccountSettingsPage extends React.Component {
|
|||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
|
{getConfig().COACHING_ENABLED
|
||||||
<AdditionalProfileFieldsSlot />
|
&& this.props.formValues.coaching.eligible_for_coaching
|
||||||
|
&& (
|
||||||
|
<CoachingToggle
|
||||||
|
name="coaching"
|
||||||
|
phone_number={this.props.formValues.phone_number}
|
||||||
|
coaching={this.props.formValues.coaching}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="account-section pt-3 mb-6" id="social-media">
|
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
|
||||||
|
<div className="account-section pt-3 mb-5" id="social-media">
|
||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
|
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -764,19 +726,16 @@ class AccountSettingsPage extends React.Component {
|
|||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
<EditableField
|
<EditableField
|
||||||
name="social_link_x"
|
name="social_link_twitter"
|
||||||
type="text"
|
type="text"
|
||||||
value={this.props.formValues.social_link_x}
|
value={this.props.formValues.social_link_twitter}
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter'])}
|
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter'])}
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="border border-light-700" />
|
|
||||||
<div className="mt-6" id="notifications" ref={this.navLinkRefs['#notifications']}>
|
<div className="account-section pt-3 mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
||||||
<NotificationSettings />
|
|
||||||
</div>
|
|
||||||
<div className="account-section mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
|
||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -818,15 +777,13 @@ class AccountSettingsPage extends React.Component {
|
|||||||
<ThirdPartyAuth />
|
<ThirdPartyAuth />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{getConfig().ENABLE_ACCOUNT_DELETION && (
|
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
||||||
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
<DeleteAccount
|
||||||
<DeleteAccount
|
isVerifiedAccount={this.props.isActive}
|
||||||
isVerifiedAccount={this.props.isActive}
|
hasLinkedTPA={hasLinkedTPA}
|
||||||
hasLinkedTPA={hasLinkedTPA}
|
/>
|
||||||
canDeleteAccount={this.canDeleteAccount()}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -855,7 +812,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="page__account-settings py-5" size="xl">
|
<div className="page__account-settings container-fluid py-5">
|
||||||
{this.renderDuplicateTpaProviderMessage()}
|
{this.renderDuplicateTpaProviderMessage()}
|
||||||
<h1 className="mb-4">
|
<h1 className="mb-4">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.page.heading'])}
|
{this.props.intl.formatMessage(messages['account.settings.page.heading'])}
|
||||||
@@ -863,7 +820,9 @@ class AccountSettingsPage extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<JumpNav />
|
<JumpNav
|
||||||
|
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-9">
|
<div className="col-md-9">
|
||||||
{loading ? this.renderLoading() : null}
|
{loading ? this.renderLoading() : null}
|
||||||
@@ -872,7 +831,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -891,23 +850,25 @@ AccountSettingsPage.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
secondary_email: PropTypes.string,
|
secondary_email: PropTypes.string,
|
||||||
secondary_email_enabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
secondary_email_enabled: PropTypes.bool,
|
||||||
year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
country: PropTypes.string,
|
country: PropTypes.string,
|
||||||
level_of_education: PropTypes.string,
|
level_of_education: PropTypes.string,
|
||||||
gender: PropTypes.string,
|
gender: PropTypes.string,
|
||||||
extended_profile: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
field_name: PropTypes.string,
|
|
||||||
field_value: PropTypes.string,
|
|
||||||
})),
|
|
||||||
language_proficiencies: PropTypes.string,
|
language_proficiencies: PropTypes.string,
|
||||||
pending_name_change: PropTypes.string,
|
pending_name_change: PropTypes.string,
|
||||||
phone_number: PropTypes.string,
|
phone_number: PropTypes.string,
|
||||||
social_link_linkedin: PropTypes.string,
|
social_link_linkedin: PropTypes.string,
|
||||||
social_link_facebook: PropTypes.string,
|
social_link_facebook: PropTypes.string,
|
||||||
social_link_x: PropTypes.string,
|
social_link_twitter: PropTypes.string,
|
||||||
time_zone: PropTypes.string,
|
time_zone: PropTypes.string,
|
||||||
|
coaching: PropTypes.shape({
|
||||||
|
coaching_consent: PropTypes.bool.isRequired,
|
||||||
|
user: PropTypes.number.isRequired,
|
||||||
|
eligible_for_coaching: PropTypes.bool.isRequired,
|
||||||
|
}),
|
||||||
state: PropTypes.string,
|
state: PropTypes.string,
|
||||||
|
shouldDisplayDemographicsSection: PropTypes.bool,
|
||||||
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
@@ -915,7 +876,6 @@ AccountSettingsPage.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
useVerifiedNameForCerts: PropTypes.bool,
|
useVerifiedNameForCerts: PropTypes.bool,
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
country: PropTypes.string,
|
|
||||||
}),
|
}),
|
||||||
drafts: PropTypes.shape({}),
|
drafts: PropTypes.shape({}),
|
||||||
formErrors: PropTypes.shape({
|
formErrors: PropTypes.shape({
|
||||||
@@ -948,16 +908,12 @@ AccountSettingsPage.propTypes = {
|
|||||||
saveSettings: PropTypes.func.isRequired,
|
saveSettings: PropTypes.func.isRequired,
|
||||||
fetchSettings: PropTypes.func.isRequired,
|
fetchSettings: PropTypes.func.isRequired,
|
||||||
beginNameChange: PropTypes.func.isRequired,
|
beginNameChange: PropTypes.func.isRequired,
|
||||||
fetchNotificationPreferences: PropTypes.func.isRequired,
|
|
||||||
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||||
connected: PropTypes.bool,
|
connected: PropTypes.bool,
|
||||||
})),
|
})),
|
||||||
nameChangeModal: PropTypes.oneOfType([
|
nameChangeModal: PropTypes.shape({
|
||||||
PropTypes.shape({
|
formId: PropTypes.string,
|
||||||
formId: PropTypes.string,
|
}),
|
||||||
}),
|
|
||||||
PropTypes.bool,
|
|
||||||
]),
|
|
||||||
verifiedName: PropTypes.shape({
|
verifiedName: PropTypes.shape({
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
@@ -975,14 +931,6 @@ AccountSettingsPage.propTypes = {
|
|||||||
proctored_exam_attempt_id: PropTypes.number,
|
proctored_exam_attempt_id: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
navigate: PropTypes.func.isRequired,
|
|
||||||
location: PropTypes.string.isRequired,
|
|
||||||
countriesCodesList: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
value: PropTypes.string.isRequired,
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AccountSettingsPage.defaultProps = {
|
AccountSettingsPage.defaultProps = {
|
||||||
@@ -992,7 +940,6 @@ AccountSettingsPage.defaultProps = {
|
|||||||
committedValues: {
|
committedValues: {
|
||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
verified_name: null,
|
verified_name: null,
|
||||||
country: '',
|
|
||||||
},
|
},
|
||||||
drafts: {},
|
drafts: {},
|
||||||
formErrors: {},
|
formErrors: {},
|
||||||
@@ -1005,19 +952,17 @@ AccountSettingsPage.defaultProps = {
|
|||||||
tpaProviders: [],
|
tpaProviders: [],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
secondary_email_enabled: false,
|
secondary_email_enabled: false,
|
||||||
nameChangeModal: {} || false,
|
nameChangeModal: {},
|
||||||
verifiedName: null,
|
verifiedName: null,
|
||||||
mostRecentVerifiedName: {},
|
mostRecentVerifiedName: {},
|
||||||
verifiedNameHistory: [],
|
verifiedNameHistory: [],
|
||||||
countriesCodesList: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withLocation(withNavigate(connect(accountSettingsPageSelector, {
|
export default connect(accountSettingsPageSelector, {
|
||||||
fetchNotificationPreferences,
|
|
||||||
fetchSettings,
|
fetchSettings,
|
||||||
saveSettings,
|
saveSettings,
|
||||||
saveMultipleSettings,
|
saveMultipleSettings,
|
||||||
updateDraft,
|
updateDraft,
|
||||||
fetchSiteLanguages,
|
fetchSiteLanguages,
|
||||||
beginNameChange,
|
beginNameChange,
|
||||||
})(injectIntl(AccountSettingsPage))));
|
})(injectIntl(AccountSettingsPage));
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Profile Information',
|
defaultMessage: 'Profile Information',
|
||||||
description: 'The profile information section heading.',
|
description: 'The profile information section heading.',
|
||||||
},
|
},
|
||||||
|
'account.settings.section.demographics.information': {
|
||||||
|
id: 'account.settings.section.demographics.information',
|
||||||
|
defaultMessage: 'Optional Information',
|
||||||
|
description: 'The optional information section heading.',
|
||||||
|
},
|
||||||
'account.settings.section.site.preferences': {
|
'account.settings.section.site.preferences': {
|
||||||
id: 'account.settings.section.site.preferences',
|
id: 'account.settings.section.site.preferences',
|
||||||
defaultMessage: 'Site Preferences',
|
defaultMessage: 'Site Preferences',
|
||||||
@@ -141,6 +146,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.',
|
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.',
|
||||||
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring and will appear on certificates.',
|
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring and will appear on certificates.',
|
||||||
},
|
},
|
||||||
|
'account.settings.field.name.verified.verification.alert': {
|
||||||
|
id: 'account.settings.field.name.verified.verification.help',
|
||||||
|
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
|
||||||
|
description: 'Form label instructing the user to enter the name on their ID.',
|
||||||
|
},
|
||||||
'account.settings.field.full.name.help.text.submitted': {
|
'account.settings.field.full.name.help.text.submitted': {
|
||||||
id: 'account.settings.field.full.name.help.text.submitted',
|
id: 'account.settings.field.full.name.help.text.submitted',
|
||||||
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
||||||
@@ -401,8 +411,8 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'No formal education',
|
defaultMessage: 'No formal education',
|
||||||
description: 'Selected by the user to describe their education.',
|
description: 'Selected by the user to describe their education.',
|
||||||
},
|
},
|
||||||
'account.settings.field.education.levels.other': {
|
'account.settings.field.education.levels.o': {
|
||||||
id: 'account.settings.field.education.levels.other',
|
id: 'account.settings.field.education.levels.o',
|
||||||
defaultMessage: 'Other education',
|
defaultMessage: 'Other education',
|
||||||
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
||||||
},
|
},
|
||||||
@@ -509,15 +519,15 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Delete My Account',
|
defaultMessage: 'Delete My Account',
|
||||||
description: 'Header for the user account deletion area',
|
description: 'Header for the user account deletion area',
|
||||||
},
|
},
|
||||||
'account.settings.field.social.platform.name.xTwitter': {
|
'account.settings.field.social.platform.name.twitter': {
|
||||||
id: 'account.settings.field.social.platform.name.xTwitter',
|
id: 'account.settings.field.social.platform.name.twitter',
|
||||||
defaultMessage: 'X (Twitter)',
|
defaultMessage: 'Twitter',
|
||||||
description: 'Label for X (Twitter)',
|
description: 'Label for Twitter',
|
||||||
},
|
},
|
||||||
'account.settings.field.social.platform.name.xTwitter.empty': {
|
'account.settings.field.social.platform.name.twitter.empty': {
|
||||||
id: 'account.settings.field.social.platform.name.xTwitter.empty',
|
id: 'account.settings.field.social.platform.name.twitter.empty',
|
||||||
defaultMessage: 'Add X profile',
|
defaultMessage: 'Add Twitter profile',
|
||||||
description: 'Placeholder for an empty X field',
|
description: 'Placeholder for an empty Twitter field',
|
||||||
},
|
},
|
||||||
|
|
||||||
'account.settings.field.social.platform.name.facebook': {
|
'account.settings.field.social.platform.name.facebook': {
|
||||||
@@ -555,26 +565,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'No value set.',
|
defaultMessage: 'No value set.',
|
||||||
description: 'The placeholder for an empty but uneditable field when there is no administrator',
|
description: 'The placeholder for an empty but uneditable field when there is no administrator',
|
||||||
},
|
},
|
||||||
'notification.preferences.notifications.label': {
|
|
||||||
id: 'notification.preferences.notifications.label',
|
|
||||||
defaultMessage: 'Notifications',
|
|
||||||
description: 'Label for Notifications',
|
|
||||||
},
|
|
||||||
'account.settings.field.work.experience': {
|
|
||||||
id: 'account.settings.work.experience',
|
|
||||||
defaultMessage: 'Work Experience',
|
|
||||||
description: 'Label for account settings Work experience field.',
|
|
||||||
},
|
|
||||||
'account.settings.field.work.experience.empty': {
|
|
||||||
id: 'account.settings.field.work.experience.empty',
|
|
||||||
defaultMessage: 'Add work experience',
|
|
||||||
description: 'Placeholder for empty account settings work experience field.',
|
|
||||||
},
|
|
||||||
'account.settings.field.work.experience.options.empty': {
|
|
||||||
id: 'account.settings.field.work.experience.options.empty',
|
|
||||||
defaultMessage: 'Select work experience',
|
|
||||||
description: 'Placeholder for the work experience levels dropdown.',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { AppContext } from '@edx/frontend-platform/react';
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Button, Hyperlink } from '@openedx/paragon';
|
import { Button, Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
import { betaLanguageBannerSelector } from './data/selectors';
|
import { betaLanguageBannerSelector } from './data/selectors';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
@@ -49,9 +49,6 @@ class BetaLanguageBanner extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const savedLanguage = this.getSiteLanguageEntry(this.context.locale);
|
const savedLanguage = this.getSiteLanguageEntry(this.context.locale);
|
||||||
if (!savedLanguage) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const isSavedLanguageReleased = savedLanguage.released === true;
|
const isSavedLanguageReleased = savedLanguage.released === true;
|
||||||
const noPreviousLanguageSet = this.props.siteLanguage.previousValue === null;
|
const noPreviousLanguageSet = this.props.siteLanguage.previousValue === null;
|
||||||
if (isSavedLanguageReleased || noPreviousLanguageSet) {
|
if (isSavedLanguageReleased || noPreviousLanguageSet) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
||||||
@@ -11,11 +11,11 @@ import { editableFieldSelector } from './data/selectors';
|
|||||||
import { saveSettingsReset } from './data/actions';
|
import { saveSettingsReset } from './data/actions';
|
||||||
|
|
||||||
const DOBModal = (props) => {
|
const DOBModal = (props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const {
|
const {
|
||||||
saveState,
|
saveState,
|
||||||
error,
|
error,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
intl,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -56,7 +56,7 @@ const DOBModal = (props) => {
|
|||||||
function renderErrors() {
|
function renderErrors() {
|
||||||
if (saveState === 'error' || error) {
|
if (saveState === 'error' || error) {
|
||||||
return (
|
return (
|
||||||
<Form.Control.Feedback type="invalid" key="general-error" data-testid="error-message">
|
<Form.Control.Feedback type="invalid" key="general-error">
|
||||||
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
|
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
|
||||||
</Form.Control.Feedback>
|
</Form.Control.Feedback>
|
||||||
);
|
);
|
||||||
@@ -72,7 +72,7 @@ const DOBModal = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button variant="primary" onClick={open} data-testid="open-modal-button">
|
<Button variant="primary" onClick={open}>
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
|
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
|
||||||
</Button>
|
</Button>
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
@@ -81,27 +81,25 @@ const DOBModal = (props) => {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
hasCloseButton={false}
|
hasCloseButton={false}
|
||||||
variant="default"
|
variant="default"
|
||||||
data-testid="dob-modal"
|
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit} data-testid="dob-form">
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
||||||
<ModalDialog.Header>
|
<ModalDialog.Header>
|
||||||
<ModalDialog.Title data-testid="modal-title">
|
<ModalDialog.Title>
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
||||||
</ModalDialog.Title>
|
</ModalDialog.Title>
|
||||||
</ModalDialog.Header>
|
</ModalDialog.Header>
|
||||||
|
|
||||||
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
|
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
|
||||||
<p data-testid="help-text">{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
|
<p>{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label data-testid="month-label">
|
<Form.Label>
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.month'])}
|
{intl.formatMessage(messages['account.settings.field.dob.month'])}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
name="month"
|
name="month"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
data-testid="month-select"
|
|
||||||
>
|
>
|
||||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.month.default'])}</option>
|
<option value="">{intl.formatMessage(messages['account.settings.field.dob.month.default'])}</option>
|
||||||
{[...Array(12).keys()].map(month => (
|
{[...Array(12).keys()].map(month => (
|
||||||
@@ -110,14 +108,13 @@ const DOBModal = (props) => {
|
|||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label data-testid="year-label">
|
<Form.Label>
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.year'])}
|
{intl.formatMessage(messages['account.settings.field.dob.year'])}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
name="year"
|
name="year"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
data-testid="year-select"
|
|
||||||
>
|
>
|
||||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.year.default'])}</option>
|
<option value="">{intl.formatMessage(messages['account.settings.field.dob.year.default'])}</option>
|
||||||
{YEAR_OF_BIRTH_OPTIONS.map(year => (
|
{YEAR_OF_BIRTH_OPTIONS.map(year => (
|
||||||
@@ -130,7 +127,7 @@ const DOBModal = (props) => {
|
|||||||
|
|
||||||
<ModalDialog.Footer>
|
<ModalDialog.Footer>
|
||||||
<ActionRow>
|
<ActionRow>
|
||||||
<ModalDialog.CloseButton variant="tertiary" data-testid="cancel-button">
|
<ModalDialog.CloseButton variant="tertiary">
|
||||||
Cancel
|
Cancel
|
||||||
</ModalDialog.CloseButton>
|
</ModalDialog.CloseButton>
|
||||||
<StatefulButton
|
<StatefulButton
|
||||||
@@ -140,7 +137,6 @@ const DOBModal = (props) => {
|
|||||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||||
}}
|
}}
|
||||||
disabledStates={['unedited']}
|
disabledStates={['unedited']}
|
||||||
data-testid="submit-button"
|
|
||||||
/>
|
/>
|
||||||
</ActionRow>
|
</ActionRow>
|
||||||
</ModalDialog.Footer>
|
</ModalDialog.Footer>
|
||||||
@@ -155,6 +151,7 @@ DOBModal.propTypes = {
|
|||||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
DOBModal.defaultProps = {
|
DOBModal.defaultProps = {
|
||||||
@@ -162,4 +159,4 @@ DOBModal.defaultProps = {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(editableFieldSelector)(DOBModal);
|
export default connect(editableFieldSelector)(injectIntl(DOBModal));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
import {
|
import {
|
||||||
Button, Form, StatefulButton,
|
Button, Form, StatefulButton,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@ const EditableField = (props) => {
|
|||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
isGrayedOut,
|
isGrayedOut,
|
||||||
|
intl,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -84,13 +84,9 @@ const EditableField = (props) => {
|
|||||||
if (!confirmationMessageDefinition || !confirmationValue) {
|
if (!confirmationMessageDefinition || !confirmationValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return intl.formatMessage(confirmationMessageDefinition, {
|
||||||
<span data-testid="editable-field-confirmation">
|
value: confirmationValue,
|
||||||
{intl.formatMessage(confirmationMessageDefinition, {
|
});
|
||||||
value: confirmationValue,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -99,7 +95,7 @@ const EditableField = (props) => {
|
|||||||
cases={{
|
cases={{
|
||||||
editing: (
|
editing: (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleSubmit} data-testid="editable-field-form">
|
<form onSubmit={handleSubmit}>
|
||||||
<Form.Group
|
<Form.Group
|
||||||
controlId={id}
|
controlId={id}
|
||||||
isInvalid={error != null}
|
isInvalid={error != null}
|
||||||
@@ -112,11 +108,10 @@ const EditableField = (props) => {
|
|||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
data-testid="editable-field-textbox"
|
|
||||||
{...others}
|
{...others}
|
||||||
/>
|
/>
|
||||||
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
||||||
{error != null && <Form.Control.Feedback hasIcon={false} data-testid="editable-field-error">{error}</Form.Control.Feedback>}
|
{error != null && <Form.Control.Feedback hasIcon={false}>{error}</Form.Control.Feedback>}
|
||||||
{others.children}
|
{others.children}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<p>
|
<p>
|
||||||
@@ -138,21 +133,16 @@ const EditableField = (props) => {
|
|||||||
if (saveState === 'pending') { e.preventDefault(); }
|
if (saveState === 'pending') { e.preventDefault(); }
|
||||||
}}
|
}}
|
||||||
disabledStates={[]}
|
disabledStates={[]}
|
||||||
data-testid="editable-field-save"
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
data-testid="editable-field-cancel"
|
|
||||||
data-clicked="cancel"
|
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{['name', 'verified_name'].includes(name) && (
|
{['name', 'verified_name'].includes(name) && <CertificatePreference fieldName={name} />}
|
||||||
<CertificatePreference fieldName={name} data-testid="editable-field-certificate-preference" />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
default: (
|
default: (
|
||||||
@@ -160,12 +150,12 @@ const EditableField = (props) => {
|
|||||||
<div className="d-flex align-items-start">
|
<div className="d-flex align-items-start">
|
||||||
<h6 aria-level="3">{label}</h6>
|
<h6 aria-level="3">{label}</h6>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Button variant="link" onClick={handleEdit} className="ml-3" data-testid="editable-field-edit" data-clicked="edit">
|
<Button variant="link" onClick={handleEdit} className="ml-3">
|
||||||
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />{intl.formatMessage(messages['account.settings.editable.field.action.edit'])}
|
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />{intl.formatMessage(messages['account.settings.editable.field.action.edit'])}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p data-hj-suppress className={classNames('text-truncate', { 'grayed-out': isGrayedOut })}>{renderValue(value)}</p>
|
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
|
||||||
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -197,6 +187,7 @@ EditableField.propTypes = {
|
|||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
isGrayedOut: PropTypes.bool,
|
isGrayedOut: PropTypes.bool,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
EditableField.defaultProps = {
|
EditableField.defaultProps = {
|
||||||
@@ -217,4 +208,4 @@ EditableField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(EditableField);
|
})(injectIntl(EditableField));
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, Form, StatefulButton,
|
Button, Form, StatefulButton,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
@@ -18,7 +19,6 @@ import { editableFieldSelector } from './data/selectors';
|
|||||||
import CertificatePreference from './certificate-preference/CertificatePreference';
|
import CertificatePreference from './certificate-preference/CertificatePreference';
|
||||||
|
|
||||||
const EditableSelectField = (props) => {
|
const EditableSelectField = (props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@@ -39,6 +39,7 @@ const EditableSelectField = (props) => {
|
|||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
isGrayedOut,
|
isGrayedOut,
|
||||||
|
intl,
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
@@ -97,29 +98,9 @@ const EditableSelectField = (props) => {
|
|||||||
value: confirmationValue,
|
value: confirmationValue,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const selectOptions = options.map((option) => {
|
const selectOptions = options.map(option => (
|
||||||
if (option.group) {
|
<option value={option.value} key={`${option.value}-${option.label}`}>{option.label}</option>
|
||||||
// If the option has a 'group' property, it represents an element with sub-options.
|
));
|
||||||
return (
|
|
||||||
<optgroup label={option.label} key={option.label}>
|
|
||||||
{option.group.map((subOption) => (
|
|
||||||
<option
|
|
||||||
value={subOption.value}
|
|
||||||
key={`${subOption.value}-${subOption.label}`}
|
|
||||||
disabled={subOption?.disabled}
|
|
||||||
>
|
|
||||||
{subOption.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</optgroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<option value={option.value} key={`${option.value}-${option.label}`} disabled={option?.disabled}>
|
|
||||||
{option.label}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwitchContent
|
<SwitchContent
|
||||||
@@ -226,6 +207,7 @@ EditableSelectField.propTypes = {
|
|||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
isGrayedOut: PropTypes.bool,
|
isGrayedOut: PropTypes.bool,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
EditableSelectField.defaultProps = {
|
EditableSelectField.defaultProps = {
|
||||||
@@ -247,4 +229,4 @@ EditableSelectField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(EditableSelectField);
|
})(injectIntl(EditableSelectField));
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, StatefulButton, Form, Tooltip, OverlayTrigger,
|
Button, StatefulButton, Form,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@@ -34,9 +35,9 @@ const EmailField = (props) => {
|
|||||||
onChange,
|
onChange,
|
||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
|
intl,
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -161,16 +162,7 @@ const EmailField = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<OverlayTrigger
|
<p data-hj-suppress>{renderValue()}</p>
|
||||||
placement="top"
|
|
||||||
overlay={(
|
|
||||||
<Tooltip id={`tooltip-${name}`} variant="light" className="d-sm-none">
|
|
||||||
{renderValue()}
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<p data-hj-suppress className="text-truncate">{renderValue()}</p>
|
|
||||||
</OverlayTrigger>
|
|
||||||
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
|
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -199,6 +191,7 @@ EmailField.propTypes = {
|
|||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailField.defaultProps = {
|
EmailField.defaultProps = {
|
||||||
@@ -217,4 +210,4 @@ EmailField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(EmailField);
|
})(injectIntl(EmailField));
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
import { NavHashLink } from 'react-router-hash-link';
|
import { NavHashLink } from 'react-router-hash-link';
|
||||||
import Scrollspy from 'react-scrollspy';
|
import Scrollspy from 'react-scrollspy';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
|
|
||||||
const JumpNav = () => {
|
const JumpNav = ({
|
||||||
const intl = useIntl();
|
intl,
|
||||||
|
displayDemographicsLink,
|
||||||
|
}) => {
|
||||||
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
||||||
<Scrollspy
|
<Scrollspy
|
||||||
items={[
|
items={[
|
||||||
'basic-information',
|
'basic-information',
|
||||||
'profile-information',
|
'profile-information',
|
||||||
|
'demographics-information',
|
||||||
'social-media',
|
'social-media',
|
||||||
'notifications',
|
|
||||||
'site-preferences',
|
'site-preferences',
|
||||||
'linked-accounts',
|
'linked-accounts',
|
||||||
'delete-account',
|
'delete-account',
|
||||||
]}
|
]}
|
||||||
className="list-unstyled"
|
className="list-unstyled"
|
||||||
currentClassName="font-weight-bold"
|
currentClassName="font-weight-bold"
|
||||||
offset={-64}
|
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#basic-information">
|
<NavHashLink to="#basic-information">
|
||||||
@@ -36,16 +38,19 @@ const JumpNav = () => {
|
|||||||
{intl.formatMessage(messages['account.settings.section.profile.information'])}
|
{intl.formatMessage(messages['account.settings.section.profile.information'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
|
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && displayDemographicsLink
|
||||||
|
&& (
|
||||||
|
<li>
|
||||||
|
<NavHashLink to="#demographics-information">
|
||||||
|
{intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
||||||
|
</NavHashLink>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#social-media">
|
<NavHashLink to="#social-media">
|
||||||
{intl.formatMessage(messages['account.settings.section.social.media'])}
|
{intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<NavHashLink to="#notifications">
|
|
||||||
{intl.formatMessage(messages['notification.preferences.notifications.label'])}
|
|
||||||
</NavHashLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#site-preferences">
|
<NavHashLink to="#site-preferences">
|
||||||
{intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
{intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
||||||
@@ -56,17 +61,19 @@ const JumpNav = () => {
|
|||||||
{intl.formatMessage(messages['account.settings.section.linked.accounts'])}
|
{intl.formatMessage(messages['account.settings.section.linked.accounts'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
{getConfig().ENABLE_ACCOUNT_DELETION
|
<li>
|
||||||
&& (
|
<NavHashLink to="#delete-account">
|
||||||
<li>
|
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
||||||
<NavHashLink to="#delete-account">
|
</NavHashLink>
|
||||||
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
</li>
|
||||||
</NavHashLink>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
</Scrollspy>
|
</Scrollspy>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default JumpNav;
|
JumpNav.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
displayDemographicsLink: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(JumpNav);
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ import React from 'react';
|
|||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const NotFoundPage = () => (
|
const NotFoundPage = () => (
|
||||||
<div
|
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||||
className="container-fluid d-flex py-5 justify-content-center align-items-start text-center"
|
|
||||||
data-testid="not-found-page"
|
|
||||||
>
|
|
||||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="error.notfound.message"
|
id="error.notfound.message"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Alert } from '@openedx/paragon';
|
import { Alert } from '@edx/paragon';
|
||||||
|
|
||||||
const OneTimeDismissibleAlert = (props) => {
|
const OneTimeDismissibleAlert = (props) => {
|
||||||
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TransitionReplace } from '@openedx/paragon';
|
import { TransitionReplace } from '@edx/paragon';
|
||||||
|
|
||||||
const onChildExit = (htmlNode) => {
|
const onChildExit = (htmlNode) => {
|
||||||
// If the leaving child has focus, take control and redirect it
|
// If the leaving child has focus, take control and redirect it
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.custom-switch {
|
.custom-switch {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
@@ -43,8 +44,3 @@
|
|||||||
filter: alpha(opacity = 60); /* MSIE */
|
filter: alpha(opacity = 60); /* MSIE */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#tooltip-email .small {
|
|
||||||
display: block;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
StatefulButton,
|
StatefulButton,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
closeForm,
|
closeForm,
|
||||||
@@ -22,6 +22,7 @@ import commonMessages from '../AccountSettingsPage.messages';
|
|||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const CertificatePreference = ({
|
const CertificatePreference = ({
|
||||||
|
intl,
|
||||||
fieldName,
|
fieldName,
|
||||||
originalFullName,
|
originalFullName,
|
||||||
originalVerifiedName,
|
originalVerifiedName,
|
||||||
@@ -32,7 +33,6 @@ const CertificatePreference = ({
|
|||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||||
const formId = 'useVerifiedNameForCerts';
|
const formId = 'useVerifiedNameForCerts';
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleCheckboxChange = () => {
|
const handleCheckboxChange = () => {
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
@@ -155,6 +155,7 @@ const CertificatePreference = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
CertificatePreference.propTypes = {
|
CertificatePreference.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
fieldName: PropTypes.string.isRequired,
|
fieldName: PropTypes.string.isRequired,
|
||||||
originalFullName: PropTypes.string,
|
originalFullName: PropTypes.string,
|
||||||
originalVerifiedName: PropTypes.string,
|
originalVerifiedName: PropTypes.string,
|
||||||
@@ -169,4 +170,4 @@ CertificatePreference.defaultProps = {
|
|||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(certPreferenceSelector)(CertificatePreference);
|
export default connect(certPreferenceSelector)(injectIntl(CertificatePreference));
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
|
|
||||||
import { postVerifiedNameConfig } from './service';
|
|
||||||
import { handleRequestError } from '../../data/utils';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('../../data/utils');
|
|
||||||
|
|
||||||
describe('postVerifiedNameConfig', () => {
|
|
||||||
const mockPost = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({
|
|
||||||
LMS_BASE_URL: 'http://testserver',
|
|
||||||
});
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({
|
|
||||||
post: mockPost,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('posts verified name config with useVerifiedNameForCerts = true', async () => {
|
|
||||||
const mockResponse = { data: { success: true } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const result = await postVerifiedNameConfig('testuser', { useVerifiedNameForCerts: true });
|
|
||||||
|
|
||||||
expect(getConfig).toHaveBeenCalled();
|
|
||||||
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/api/edx_name_affirmation/v1/verified_name/config',
|
|
||||||
{
|
|
||||||
username: 'testuser',
|
|
||||||
use_verified_name_for_certs: true,
|
|
||||||
},
|
|
||||||
{ headers: { Accept: 'application/json' } },
|
|
||||||
);
|
|
||||||
expect(result).toEqual(mockResponse.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('posts verified name config with useVerifiedNameForCerts = false', async () => {
|
|
||||||
const mockResponse = { data: { success: false } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const result = await postVerifiedNameConfig('anotheruser', { useVerifiedNameForCerts: false });
|
|
||||||
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/api/edx_name_affirmation/v1/verified_name/config',
|
|
||||||
{
|
|
||||||
username: 'anotheruser',
|
|
||||||
use_verified_name_for_certs: false,
|
|
||||||
},
|
|
||||||
{ headers: { Accept: 'application/json' } },
|
|
||||||
);
|
|
||||||
expect(result).toEqual(mockResponse.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls handleRequestError and throws when request fails', async () => {
|
|
||||||
const mockError = new Error('Request failed');
|
|
||||||
mockPost.mockRejectedValueOnce(mockError);
|
|
||||||
|
|
||||||
handleRequestError.mockImplementation(() => {
|
|
||||||
throw mockError;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
postVerifiedNameConfig('erroruser', { useVerifiedNameForCerts: true }),
|
|
||||||
).rejects.toThrow('Request failed');
|
|
||||||
|
|
||||||
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {
|
import {
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
import * as auth from '@edx/frontend-platform/auth';
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
import messages from '../messages';
|
|
||||||
|
|
||||||
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||||
jest.mock('react-dom', () => ({
|
ReactDOM.createPortal = node => node;
|
||||||
...jest.requireActual('react-dom'),
|
|
||||||
createPortal: jest.fn(node => node), // Mock portal behavior
|
|
||||||
}));
|
|
||||||
|
|
||||||
import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first
|
import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first
|
||||||
|
|
||||||
@@ -30,6 +28,10 @@ jest.mock('react-redux', () => ({
|
|||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
||||||
|
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
|
||||||
|
const IntlCertificatePreference = injectIntl(CertificatePreference);
|
||||||
|
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
|
|
||||||
describe('NameChange', () => {
|
describe('NameChange', () => {
|
||||||
@@ -37,10 +39,10 @@ describe('NameChange', () => {
|
|||||||
let store = {};
|
let store = {};
|
||||||
const formId = 'useVerifiedNameForCerts';
|
const formId = 'useVerifiedNameForCerts';
|
||||||
const updateDraft = 'UPDATE_DRAFT';
|
const updateDraft = 'UPDATE_DRAFT';
|
||||||
const labelText = messages['account.settings.field.name.checkbox.certificate.select'].defaultMessage;
|
const labelText = 'If checked, this name will appear on your certificates and public-facing records.';
|
||||||
|
|
||||||
const reduxWrapper = children => (
|
const reduxWrapper = children => (
|
||||||
<Router>
|
<Router history={history}>
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<Provider store={store}>{children}</Provider>
|
<Provider store={store}>{children}</Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
@@ -55,6 +57,7 @@ describe('NameChange', () => {
|
|||||||
originalVerifiedName: 'edX Verified',
|
originalVerifiedName: 'edX Verified',
|
||||||
saveState: null,
|
saveState: null,
|
||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
|
intl: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
@@ -74,7 +77,7 @@ describe('NameChange', () => {
|
|||||||
originalVerifiedName: '',
|
originalVerifiedName: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = render(reduxWrapper(<CertificatePreference {...props} />));
|
const wrapper = render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -85,7 +88,7 @@ describe('NameChange', () => {
|
|||||||
useVerifiedNameForCerts: true,
|
useVerifiedNameForCerts: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(false);
|
expect(checkbox.checked).toEqual(false);
|
||||||
@@ -100,7 +103,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('triggers modal when attempting to uncheck checkbox', () => {
|
it('triggers modal when attempting to uncheck checkbox', () => {
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(true);
|
expect(checkbox.checked).toEqual(true);
|
||||||
@@ -112,7 +115,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates draft when changing radio value', () => {
|
it('updates draft when changing radio value', () => {
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -130,7 +133,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears draft on cancel', () => {
|
it('clears draft on cancel', () => {
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -143,7 +146,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('submits', () => {
|
it('submits', () => {
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -151,7 +154,7 @@ describe('NameChange', () => {
|
|||||||
const submitButton = screen.getByText('Choose name');
|
const submitButton = screen.getByText('Choose name');
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
expect(mockDispatch).toHaveBeenCalledWith({
|
expect(mockDispatch).toHaveBeenCalledWith({
|
||||||
payload: { formId, commitValues: false, extendedProfile: {} },
|
payload: { formId, commitValues: false },
|
||||||
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -163,7 +166,7 @@ describe('NameChange', () => {
|
|||||||
useVerifiedNameForCerts: true,
|
useVerifiedNameForCerts: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<CertificatePreference {...props} />));
|
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(true);
|
expect(checkbox.checked).toEqual(true);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`NameChange does not render if there is no verified name 1`] = `
|
exports[`NameChange does not render if there is no verified name 1`] = `
|
||||||
{
|
Object {
|
||||||
"asFragment": [Function],
|
"asFragment": [Function],
|
||||||
"baseElement": <body>
|
"baseElement": <body>
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
267
src/account-settings/coaching/CoachingConsent.jsx
Normal file
267
src/account-settings/coaching/CoachingConsent.jsx
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
|
||||||
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink } from '@edx/paragon';
|
||||||
|
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
|
import PageLoading from '../PageLoading';
|
||||||
|
import CoachingConsentForm from './CoachingConsentForm';
|
||||||
|
import messages from './CoachingConsent.messages';
|
||||||
|
import LogoSVG from '../../logo.svg';
|
||||||
|
import { fetchSettings } from '../data/actions';
|
||||||
|
import { coachingConsentPageSelector } from '../data/selectors';
|
||||||
|
|
||||||
|
const Logo = ({ src, alt, ...attributes }) => <img src={src} alt={alt} {...attributes} />;
|
||||||
|
|
||||||
|
const SuccessMessage = (props) => (
|
||||||
|
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
|
||||||
|
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
|
||||||
|
<div className="h3">{props.header}</div>
|
||||||
|
<div>{props.message}</div>
|
||||||
|
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
|
||||||
|
{props.continue}
|
||||||
|
</Hyperlink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AutoRedirect = (props) => {
|
||||||
|
window.location.href = props.redirectUrl;
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VIEWS = {
|
||||||
|
NOT_LOADED: 'NOT_LOADED',
|
||||||
|
LOADED: 'LOADED',
|
||||||
|
SUCCESS: 'SUCCESS',
|
||||||
|
SUCCESS_PENDING: 'SUCCESS_PENDING',
|
||||||
|
DECLINED: 'DECLINED',
|
||||||
|
DECLINE_PENDING: 'DECLINE_PENDING',
|
||||||
|
};
|
||||||
|
|
||||||
|
class CoachingConsent extends React.Component {
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
// Used to redirect back to the courseware.
|
||||||
|
const nextUrl = this.sanitizeForwardingUrl(getQueryParameters().next);
|
||||||
|
this.state = {
|
||||||
|
redirectUrl: nextUrl || `${getConfig().LMS_BASE_URL}/dashboard/`,
|
||||||
|
formErrors: {},
|
||||||
|
formSubmitted: false,
|
||||||
|
declineSubmitted: false,
|
||||||
|
submissionSuccess: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
this.declineCoaching = this.declineCoaching.bind(this);
|
||||||
|
this.patchUsingCoachingConsentForm = this.patchUsingCoachingConsentForm.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const fullName = e.target.fullName.value;
|
||||||
|
const phoneNumber = e.target.phoneNumber.value;
|
||||||
|
const body = {
|
||||||
|
coaching_consent: true,
|
||||||
|
consent_form_seen: true,
|
||||||
|
phone_number: phoneNumber,
|
||||||
|
full_name: fullName,
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
formErrors: {},
|
||||||
|
formSubmitted: true,
|
||||||
|
declineSubmitted: false,
|
||||||
|
}, () => this.patchUsingCoachingConsentForm(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitizeForwardingUrl(url) {
|
||||||
|
// Redirect to root of MFE if invalid next param is sent
|
||||||
|
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchUsingCoachingConsentForm(body) {
|
||||||
|
const { userId } = getAuthenticatedUser();
|
||||||
|
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/coaching_consent/${userId}/`;
|
||||||
|
let formErrors = {};
|
||||||
|
const data = await getAuthenticatedHttpClient()
|
||||||
|
.patch(requestUrl, body)
|
||||||
|
.catch((error) => {
|
||||||
|
if (get(error, 'customAttributes.httpErrorResponseData')) {
|
||||||
|
formErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
||||||
|
} else {
|
||||||
|
formErrors = { full_name: 'Something went wrong. Please try again.' };
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
submissionSuccess: false,
|
||||||
|
formErrors,
|
||||||
|
formSubmitted: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (get(data, 'status') === 200) {
|
||||||
|
this.setState({ submissionSuccess: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declineCoaching(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const body = {
|
||||||
|
coaching_consent: false,
|
||||||
|
consent_form_seen: true,
|
||||||
|
};
|
||||||
|
this.setState({
|
||||||
|
formErrors: {},
|
||||||
|
formSubmitted: false,
|
||||||
|
declineSubmitted: true,
|
||||||
|
}, () => this.patchUsingCoachingConsentForm(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
renderView(currentView) {
|
||||||
|
switch (currentView) {
|
||||||
|
case VIEWS.NOT_LOADED:
|
||||||
|
return <PageLoading srMessage="" />;
|
||||||
|
case VIEWS.LOADED:
|
||||||
|
return (
|
||||||
|
<CoachingConsentForm
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
declineCoaching={this.declineCoaching}
|
||||||
|
formErrors={this.state.formErrors}
|
||||||
|
formValues={this.props.formValues}
|
||||||
|
redirectUrl={this.state.redirectUrl}
|
||||||
|
profileDataManager={this.props.profileDataManager}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case VIEWS.SUCCESS_PENDING:
|
||||||
|
return <PageLoading srMessage="Submitting..." />;
|
||||||
|
case VIEWS.SUCCESS:
|
||||||
|
return (
|
||||||
|
<SuccessMessage
|
||||||
|
continueUrl={this.state.redirectUrl}
|
||||||
|
header={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.header'])}
|
||||||
|
message={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.message'])}
|
||||||
|
continue={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.continue'])}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case VIEWS.DECLINE_PENDING:
|
||||||
|
return <PageLoading srMessage="Redirecting..." />;
|
||||||
|
case VIEWS.DECLINED:
|
||||||
|
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
|
||||||
|
default:
|
||||||
|
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loaded } = this.props;
|
||||||
|
const formHasErrors = Object.keys(this.state.formErrors).length > 0;
|
||||||
|
let currentView = null;
|
||||||
|
// This amount of logic was making the template very hard to read, so I broke it out into views.
|
||||||
|
if (!loaded) {
|
||||||
|
currentView = VIEWS.NOT_LOADED;
|
||||||
|
} else if (this.state.formSubmitted && !formHasErrors) {
|
||||||
|
if (this.state.submissionSuccess) {
|
||||||
|
currentView = VIEWS.SUCCESS;
|
||||||
|
} else {
|
||||||
|
currentView = VIEWS.SUCCESS_PENDING;
|
||||||
|
}
|
||||||
|
} else if (this.state.declineSubmitted && !formHasErrors) {
|
||||||
|
if (this.state.submissionSuccess) {
|
||||||
|
currentView = VIEWS.DECLINED;
|
||||||
|
} else {
|
||||||
|
currentView = VIEWS.DECLINE_PENDING;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentView = VIEWS.LOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
<div className="w-100 d-flex justify-content-center align-items-center shadow coaching-header">
|
||||||
|
<Logo
|
||||||
|
className="logo"
|
||||||
|
src={LogoSVG}
|
||||||
|
alt="Logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{this.renderView(currentView)}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logo.defaultProps = {
|
||||||
|
src: '',
|
||||||
|
alt: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
Logo.propTypes = {
|
||||||
|
src: PropTypes.string,
|
||||||
|
alt: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
SuccessMessage.defaultProps = {
|
||||||
|
header: '',
|
||||||
|
message: '',
|
||||||
|
continueUrl: '',
|
||||||
|
continue: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
SuccessMessage.propTypes = {
|
||||||
|
header: PropTypes.string,
|
||||||
|
message: PropTypes.string,
|
||||||
|
continueUrl: PropTypes.string,
|
||||||
|
continue: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoRedirect.defaultProps = {
|
||||||
|
redirectUrl: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoRedirect.propTypes = {
|
||||||
|
redirectUrl: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoachingConsent.defaultProps = {
|
||||||
|
loaded: false,
|
||||||
|
profileDataManager: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoachingConsent.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
loaded: PropTypes.bool,
|
||||||
|
formValues: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
phone_number: PropTypes.string,
|
||||||
|
coaching: PropTypes.shape({
|
||||||
|
coaching_consent: PropTypes.bool.isRequired,
|
||||||
|
user: PropTypes.number.isRequired,
|
||||||
|
eligible_for_coaching: PropTypes.bool.isRequired,
|
||||||
|
consent_form_seen: PropTypes.bool.isRequired,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
|
formErrors: PropTypes.shape({
|
||||||
|
coaching: PropTypes.shape({}),
|
||||||
|
}).isRequired,
|
||||||
|
confirmationValues: PropTypes.shape({
|
||||||
|
coaching: PropTypes.shape({}),
|
||||||
|
name: PropTypes.shape({}),
|
||||||
|
phone_number: PropTypes.shape({}),
|
||||||
|
}).isRequired,
|
||||||
|
fetchSettings: PropTypes.func.isRequired,
|
||||||
|
profileDataManager: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(coachingConsentPageSelector, {
|
||||||
|
fetchSettings,
|
||||||
|
})(injectIntl(CoachingConsent));
|
||||||
66
src/account-settings/coaching/CoachingConsent.messages.js
Normal file
66
src/account-settings/coaching/CoachingConsent.messages.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'account.settings.coaching.consent.welcome.header': {
|
||||||
|
id: 'account.settings.coaching.consent.welcome.header',
|
||||||
|
defaultMessage: 'Let’s get started.',
|
||||||
|
description: 'The welcome header for consent form.',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.welcome.subheader': {
|
||||||
|
id: 'account.settings.coaching.consent.welcome.subheader',
|
||||||
|
defaultMessage: "We're here for you from start to finish",
|
||||||
|
description: 'The welcome subheader for consent form.',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.description': {
|
||||||
|
id: 'account.settings.coaching.consent.description',
|
||||||
|
defaultMessage: "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
|
||||||
|
description: 'Text describing what Coaching is.',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.text-messaging.disclaimer': {
|
||||||
|
id: 'account.settings.coaching.consent.text-messaging.disclaimer',
|
||||||
|
defaultMessage: '* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.',
|
||||||
|
description: 'Text describing what Coaching is.',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.accept-coaching': {
|
||||||
|
id: 'account.settings.coaching.consent.accept-coaching',
|
||||||
|
defaultMessage: 'Sign up for coaching',
|
||||||
|
description: 'Text to confirm coaching enablement',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.decline-coaching': {
|
||||||
|
id: 'account.settings.coaching.consent.decline-coaching',
|
||||||
|
defaultMessage: 'I prefer not to be contacted with free coaching services',
|
||||||
|
description: 'Text to decline coaching enablement',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.label.name': {
|
||||||
|
id: 'account.settings.coaching.consent.label.name',
|
||||||
|
defaultMessage: 'Please confirm your name',
|
||||||
|
description: 'Label for name input',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.label.phone-number': {
|
||||||
|
id: 'account.settings.coaching.consent.label.phone-number',
|
||||||
|
defaultMessage: 'Enter your mobile number',
|
||||||
|
description: 'Label for mobile phone number input',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.success.header': {
|
||||||
|
id: 'account.settings.coaching.consent.success.header',
|
||||||
|
defaultMessage: 'Success!',
|
||||||
|
description: 'Heading announcing that submission succeeded',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.success.message': {
|
||||||
|
id: 'account.settings.coaching.consent.success.message',
|
||||||
|
defaultMessage: "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
|
||||||
|
description: 'Text announcing that you have signed up and will receive texts',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.consent.success.continue': {
|
||||||
|
id: 'account.settings.coaching.consent.success.continue',
|
||||||
|
defaultMessage: 'Start my course',
|
||||||
|
description: 'Text that the user will be sent back to the courseware',
|
||||||
|
},
|
||||||
|
'account.settings.coaching.managed.support': {
|
||||||
|
id: 'account.settings.coaching.managed.support',
|
||||||
|
defaultMessage: 'support',
|
||||||
|
description: 'website support',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
129
src/account-settings/coaching/CoachingConsentForm.jsx
Normal file
129
src/account-settings/coaching/CoachingConsentForm.jsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Form, Button, Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import Alert from '../Alert';
|
||||||
|
import messages from './CoachingConsent.messages';
|
||||||
|
|
||||||
|
const ErrorMessage = (props) => <div className="alert-warning mb-2">{props.message}</div>;
|
||||||
|
|
||||||
|
const ManagedProfileAlert = ({ profileDataManager }) => (
|
||||||
|
<Alert className="alert alert-primary" role="alert">
|
||||||
|
<FormattedMessage
|
||||||
|
id="account.settings.coaching.managed.alert"
|
||||||
|
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
|
||||||
|
description="Alert message informing the user their account data is managed by a third party"
|
||||||
|
values={{
|
||||||
|
managerTitle: <b>{profileDataManager}</b>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
const CoachingForm = (props) => (
|
||||||
|
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
|
||||||
|
<h2 className="h2">
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
|
||||||
|
</h2>
|
||||||
|
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
|
||||||
|
<div>
|
||||||
|
<form onSubmit={props.onSubmit}>
|
||||||
|
<div className="py-3">
|
||||||
|
{!!props.profileDataManager && (
|
||||||
|
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
|
||||||
|
)}
|
||||||
|
<ErrorMessage message={props.formErrors.full_name} />
|
||||||
|
<label className="h6" htmlFor="fullName">
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
|
||||||
|
</label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
name="full-name"
|
||||||
|
id="fullName"
|
||||||
|
disabled={!!props.profileDataManager}
|
||||||
|
defaultValue={props.formValues.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="py-3">
|
||||||
|
<ErrorMessage message={props.formErrors.phone_number} />
|
||||||
|
<label className="h6" htmlFor="phoneNumber">
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
|
||||||
|
</label>
|
||||||
|
<Form.Control
|
||||||
|
type="text"
|
||||||
|
name="phone_number"
|
||||||
|
id="phoneNumber"
|
||||||
|
defaultValue={props.formValues.phone_number}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className=" py-3">
|
||||||
|
<p className="small font-italic">
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ErrorMessage message={props.formErrors.coaching} />
|
||||||
|
<div className="d-flex flex-column align-items-center">
|
||||||
|
<Button variant="outline-primary" className="w-100" type="submit">
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3">
|
||||||
|
<Hyperlink
|
||||||
|
className="mt-3 text-dark btn-link small"
|
||||||
|
destination={props.redirectUrl}
|
||||||
|
onClick={props.declineCoaching}
|
||||||
|
>
|
||||||
|
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
|
||||||
|
</Hyperlink>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
CoachingForm.defaultProps = {
|
||||||
|
formErrors: {
|
||||||
|
coaching: '',
|
||||||
|
name: '',
|
||||||
|
phone_number: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
CoachingForm.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
declineCoaching: PropTypes.func.isRequired,
|
||||||
|
formValues: PropTypes.shape({
|
||||||
|
name: PropTypes.string,
|
||||||
|
phone_number: PropTypes.string,
|
||||||
|
coaching: PropTypes.shape({
|
||||||
|
coaching_consent: PropTypes.bool.isRequired,
|
||||||
|
user: PropTypes.number.isRequired,
|
||||||
|
eligible_for_coaching: PropTypes.bool.isRequired,
|
||||||
|
consent_form_seen: PropTypes.bool.isRequired,
|
||||||
|
}),
|
||||||
|
}).isRequired,
|
||||||
|
formErrors: PropTypes.shape({
|
||||||
|
coaching: PropTypes.string,
|
||||||
|
full_name: PropTypes.string,
|
||||||
|
phone_number: PropTypes.string,
|
||||||
|
}),
|
||||||
|
redirectUrl: PropTypes.string.isRequired,
|
||||||
|
profileDataManager: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorMessage.defaultProps = {
|
||||||
|
message: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
ErrorMessage.propTypes = {
|
||||||
|
message: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
ManagedProfileAlert.propTypes = {
|
||||||
|
profileDataManager: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(CoachingForm);
|
||||||
103
src/account-settings/coaching/CoachingToggle.jsx
Normal file
103
src/account-settings/coaching/CoachingToggle.jsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Form } from '@edx/paragon';
|
||||||
|
import messages from './CoachingToggle.messages';
|
||||||
|
import { editableFieldSelector } from '../data/selectors';
|
||||||
|
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
|
||||||
|
import EditableField from '../EditableField';
|
||||||
|
|
||||||
|
const CoachingToggle = (props) => (
|
||||||
|
<>
|
||||||
|
<EditableField
|
||||||
|
name="phone_number"
|
||||||
|
type="text"
|
||||||
|
value={props.phone_number}
|
||||||
|
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
|
||||||
|
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
|
||||||
|
onChange={props.updateDraft}
|
||||||
|
onSubmit={() => {
|
||||||
|
const { coaching } = props;
|
||||||
|
if (coaching.coaching_consent === true) {
|
||||||
|
return props.saveMultipleSettings([
|
||||||
|
{
|
||||||
|
formId: 'coaching',
|
||||||
|
commitValues: {
|
||||||
|
...coaching,
|
||||||
|
phone_number: props.phone_number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
formId: 'phone_number',
|
||||||
|
commitValues: props.phone_number,
|
||||||
|
},
|
||||||
|
], 'phone_number');
|
||||||
|
}
|
||||||
|
return props.saveSettings('phone_number', props.phone_number);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Group
|
||||||
|
isInvalid={!!props.error}
|
||||||
|
className="custom-control custom-switch"
|
||||||
|
>
|
||||||
|
<Form.Switch
|
||||||
|
name={props.name}
|
||||||
|
className="custom-control-input"
|
||||||
|
disabled={props.saveState === 'pending'}
|
||||||
|
type="checkbox"
|
||||||
|
id="coachingConsent"
|
||||||
|
checked={props.coaching.coaching_consent}
|
||||||
|
helperText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
|
||||||
|
value={props.coaching.coaching_consent}
|
||||||
|
onChange={async (e) => {
|
||||||
|
const { name } = e.target;
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
const { user, eligible_for_coaching } = props.coaching;
|
||||||
|
const value = {
|
||||||
|
user,
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
eligible_for_coaching,
|
||||||
|
coaching_consent: e.target.checked,
|
||||||
|
};
|
||||||
|
props.saveSettings(name, value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}
|
||||||
|
</Form.Switch>
|
||||||
|
{!!props.error && (
|
||||||
|
<Form.Control.Feedback>
|
||||||
|
{props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
)}
|
||||||
|
</Form.Group>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
CoachingToggle.defaultProps = {
|
||||||
|
phone_number: '',
|
||||||
|
error: '',
|
||||||
|
saveState: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
CoachingToggle.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
error: PropTypes.string,
|
||||||
|
coaching: PropTypes.shape({
|
||||||
|
coaching_consent: PropTypes.bool.isRequired,
|
||||||
|
user: PropTypes.number.isRequired,
|
||||||
|
eligible_for_coaching: PropTypes.bool.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||||
|
saveSettings: PropTypes.func.isRequired,
|
||||||
|
saveMultipleSettings: PropTypes.func.isRequired,
|
||||||
|
updateDraft: PropTypes.func.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
phone_number: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(editableFieldSelector, {
|
||||||
|
saveSettings,
|
||||||
|
updateDraft,
|
||||||
|
saveMultipleSettings,
|
||||||
|
})(injectIntl(CoachingToggle));
|
||||||
31
src/account-settings/coaching/CoachingToggle.messages.js
Normal file
31
src/account-settings/coaching/CoachingToggle.messages.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'account.settings.field.phone_number': {
|
||||||
|
id: 'account.settings.field.phone_number',
|
||||||
|
defaultMessage: 'Phone Number',
|
||||||
|
description: 'The label for a phone numbers setting in the user profile',
|
||||||
|
},
|
||||||
|
'account.settings.field.phone_number.empty': {
|
||||||
|
id: 'account.settings.field.phone_number.empty',
|
||||||
|
defaultMessage: 'Add a phone number',
|
||||||
|
description: 'placeholder for a profiles empty phone number field',
|
||||||
|
},
|
||||||
|
'account.settings.field.coaching_consent': {
|
||||||
|
id: 'account.settings.field.coaching_consent',
|
||||||
|
defaultMessage: 'Coaching consent',
|
||||||
|
description: 'The label for the coaching consent setting in the user profile',
|
||||||
|
},
|
||||||
|
'account.settings.field.coaching_consent.tooltip': {
|
||||||
|
id: 'account.settings.field.coaching_consent.tooltip',
|
||||||
|
defaultMessage: 'MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text ‘STOP’ at anytime to opt-out of messages.',
|
||||||
|
description: 'A tooltip explaining what coaching is and who it is for',
|
||||||
|
},
|
||||||
|
'account.settings.field.coaching_consent.error': {
|
||||||
|
id: 'account.settings.field.coaching_consent.error',
|
||||||
|
defaultMessage: 'A valid US phone number is required to opt into coaching',
|
||||||
|
description: 'An error message that displays when a user attempts to consent to coaching without first providing a phone number in their profile',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
51
src/account-settings/coaching/data/service.js
Normal file
51
src/account-settings/coaching/data/service.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get all settings related to the coaching plugin. Settings used
|
||||||
|
* by Microbachelors students.
|
||||||
|
* @param {Number} userId users are identified in the api by LMS id
|
||||||
|
*/
|
||||||
|
export async function getCoachingPreferences(userId) {
|
||||||
|
let data = {};
|
||||||
|
try {
|
||||||
|
({ data } = await getAuthenticatedHttpClient()
|
||||||
|
.get(`${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`));
|
||||||
|
} catch (error) {
|
||||||
|
// If a user isn't active the API call will fail with a lack of credentials.
|
||||||
|
data = {
|
||||||
|
coaching_consent: false,
|
||||||
|
user: userId,
|
||||||
|
eligible_for_coaching: false,
|
||||||
|
consent_form_seen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* patch all of the settings related to coaching.
|
||||||
|
* @param {Number} userId users are identified in the api by LMS id
|
||||||
|
* @param {Object} commitValues { coaching }
|
||||||
|
*/
|
||||||
|
export async function patchCoachingPreferences(userId, commitValues) {
|
||||||
|
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`;
|
||||||
|
const { coaching } = commitValues;
|
||||||
|
coaching.user = userId;
|
||||||
|
|
||||||
|
await getAuthenticatedHttpClient()
|
||||||
|
.patch(requestUrl, coaching)
|
||||||
|
.catch((error) => {
|
||||||
|
const apiError = Object.create(error);
|
||||||
|
apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
||||||
|
if (get(apiError, 'fieldErrors.phone_number')) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
apiError.fieldErrors.coaching = apiError.fieldErrors.phone_number[0];
|
||||||
|
delete apiError.fieldErrors.phone_number;
|
||||||
|
}
|
||||||
|
throw apiError;
|
||||||
|
});
|
||||||
|
return commitValues;
|
||||||
|
}
|
||||||
103
src/account-settings/coaching/test/CoachingConsent.test.jsx
Normal file
103
src/account-settings/coaching/test/CoachingConsent.test.jsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
/* eslint-disable no-import-assign */
|
||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
|
import CoachingConsent from '../CoachingConsent';
|
||||||
|
import * as selectors from '../../data/selectors';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
|
||||||
|
const IntlCoachingConsent = injectIntl(CoachingConsent);
|
||||||
|
|
||||||
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ coachingConsentPageSelector: () => ({}) })));
|
||||||
|
|
||||||
|
const mockStore = configureStore();
|
||||||
|
|
||||||
|
describe('CoachingConsent', () => {
|
||||||
|
let props = {};
|
||||||
|
let store = {};
|
||||||
|
selectors.mockClear();
|
||||||
|
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore();
|
||||||
|
props = {
|
||||||
|
fetchSettings: jest.fn(),
|
||||||
|
loaded: true,
|
||||||
|
saveState: undefined,
|
||||||
|
formValues: {
|
||||||
|
name: 'edx edx',
|
||||||
|
phone_number: '1234567890',
|
||||||
|
coaching: {
|
||||||
|
coaching_consent: true,
|
||||||
|
consent_form_seen: false,
|
||||||
|
eligible_for_coaching: true,
|
||||||
|
user: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formErrors: {},
|
||||||
|
confirmationValues: {},
|
||||||
|
profileDataManager: '',
|
||||||
|
intl: {},
|
||||||
|
};
|
||||||
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
|
patch: async () => ({
|
||||||
|
data: { status: 200 },
|
||||||
|
catch: () => {},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render', () => {
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('disables name field on enterprise user', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
profileDataManager: 'test person',
|
||||||
|
};
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('display completed box when successfully submitted', async () => {
|
||||||
|
const fakeEvent = {
|
||||||
|
preventDefault: () => {},
|
||||||
|
target: {
|
||||||
|
fullName: { value: 'edx edx' },
|
||||||
|
phoneNumber: { value: '9783028731' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = renderer.create(
|
||||||
|
reduxWrapper(<IntlCoachingConsent {...props} />),
|
||||||
|
{
|
||||||
|
// bypass the forward-ref. we don't care about focus for this one test
|
||||||
|
createNodeMock: (element) => {
|
||||||
|
if (element.type === 'button') {
|
||||||
|
// mock a focus function
|
||||||
|
return {
|
||||||
|
focus: async () => wrapper.root.findByType('form').props.onSubmit(fakeEvent),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const form = wrapper.root.findByType('form');
|
||||||
|
await act(async () => { await form.props.onSubmit(fakeEvent); });
|
||||||
|
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`CoachingConsent disables name field on enterprise user 1`] = `
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Logo"
|
||||||
|
className="logo"
|
||||||
|
src="icon/mock/path"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="h2"
|
||||||
|
>
|
||||||
|
Let’s get started.
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="py-3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert d-flex align-items-start alert alert-primary"
|
||||||
|
>
|
||||||
|
<div />
|
||||||
|
<div>
|
||||||
|
Your name is managed by
|
||||||
|
<b>
|
||||||
|
test person
|
||||||
|
</b>
|
||||||
|
. Contact your administrator for help.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
className="h6"
|
||||||
|
htmlFor="fullName"
|
||||||
|
>
|
||||||
|
Please confirm your name
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="pgn__form-control-decorator-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="has-value form-control"
|
||||||
|
defaultValue="edx edx"
|
||||||
|
disabled={true}
|
||||||
|
id="fullName"
|
||||||
|
name="full-name"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="py-3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
className="h6"
|
||||||
|
htmlFor="phoneNumber"
|
||||||
|
>
|
||||||
|
Enter your mobile number
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="pgn__form-control-decorator-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="has-value form-control"
|
||||||
|
defaultValue="1234567890"
|
||||||
|
id="phoneNumber"
|
||||||
|
name="phone_number"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className=" py-3"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="small font-italic"
|
||||||
|
>
|
||||||
|
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="d-flex flex-column align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="w-100 btn btn-outline-primary"
|
||||||
|
disabled={false}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Sign up for coaching
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-3"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
||||||
|
href="http://localhost:18000/dashboard/"
|
||||||
|
onClick={[Function]}
|
||||||
|
target="_self"
|
||||||
|
>
|
||||||
|
I prefer not to be contacted with free coaching services
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CoachingConsent display completed box when successfully submitted 1`] = `
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Logo"
|
||||||
|
className="logo"
|
||||||
|
src="icon/mock/path"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="d-flex justify-content-center align-items-center flex-column"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "50vh",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="spinner-border text-primary"
|
||||||
|
role="status"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
Submitting...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`CoachingConsent should render 1`] = `
|
||||||
|
<main>
|
||||||
|
<div
|
||||||
|
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Logo"
|
||||||
|
className="logo"
|
||||||
|
src="icon/mock/path"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="h2"
|
||||||
|
>
|
||||||
|
Let’s get started.
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<form
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="py-3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
className="h6"
|
||||||
|
htmlFor="fullName"
|
||||||
|
>
|
||||||
|
Please confirm your name
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="pgn__form-control-decorator-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="has-value form-control"
|
||||||
|
defaultValue="edx edx"
|
||||||
|
disabled={false}
|
||||||
|
id="fullName"
|
||||||
|
name="full-name"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="py-3"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
className="h6"
|
||||||
|
htmlFor="phoneNumber"
|
||||||
|
>
|
||||||
|
Enter your mobile number
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className="pgn__form-control-decorator-group"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className="has-value form-control"
|
||||||
|
defaultValue="1234567890"
|
||||||
|
id="phoneNumber"
|
||||||
|
name="phone_number"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className=" py-3"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="small font-italic"
|
||||||
|
>
|
||||||
|
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="alert-warning mb-2"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="d-flex flex-column align-items-center"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="w-100 btn btn-outline-primary"
|
||||||
|
disabled={false}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Sign up for coaching
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="mt-3"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
||||||
|
href="http://localhost:18000/dashboard/"
|
||||||
|
onClick={[Function]}
|
||||||
|
target="_self"
|
||||||
|
>
|
||||||
|
I prefer not to be contacted with free coaching services
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
`;
|
||||||
@@ -27,7 +27,6 @@ export const fetchSettingsSuccess = ({
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
countriesCodesList,
|
|
||||||
}) => ({
|
}) => ({
|
||||||
type: FETCH_SETTINGS.SUCCESS,
|
type: FETCH_SETTINGS.SUCCESS,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -36,7 +35,6 @@ export const fetchSettingsSuccess = ({
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
countriesCodesList,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,9 +77,9 @@ export const beginNameChange = (formId) => ({
|
|||||||
});
|
});
|
||||||
// SAVE SETTINGS ACTIONS
|
// SAVE SETTINGS ACTIONS
|
||||||
|
|
||||||
export const saveSettings = (formId, commitValues, extendedProfile = {}) => ({
|
export const saveSettings = (formId, commitValues) => ({
|
||||||
type: SAVE_SETTINGS.BASE,
|
type: SAVE_SETTINGS.BASE,
|
||||||
payload: { formId, commitValues, extendedProfile },
|
payload: { formId, commitValues },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveSettingsBegin = () => ({
|
export const saveSettingsBegin = () => ({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const EDUCATION_LEVELS = [
|
|||||||
'jhs',
|
'jhs',
|
||||||
'el',
|
'el',
|
||||||
'none',
|
'none',
|
||||||
'other',
|
'o',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GENDER_OPTIONS = [
|
export const GENDER_OPTIONS = [
|
||||||
@@ -34,21 +34,6 @@ export const GENDER_OPTIONS = [
|
|||||||
'm',
|
'm',
|
||||||
'o',
|
'o',
|
||||||
];
|
];
|
||||||
export const WORK_EXPERIENCE_OPTIONS = [
|
|
||||||
'',
|
|
||||||
'0',
|
|
||||||
'1',
|
|
||||||
'2',
|
|
||||||
'3',
|
|
||||||
'4',
|
|
||||||
'5',
|
|
||||||
'6',
|
|
||||||
'7',
|
|
||||||
'8',
|
|
||||||
'9',
|
|
||||||
'10+',
|
|
||||||
|
|
||||||
];
|
|
||||||
|
|
||||||
export const COUNTRY_WITH_STATES = 'US';
|
export const COUNTRY_WITH_STATES = 'US';
|
||||||
|
|
||||||
@@ -132,6 +117,6 @@ export function getStatesList(country) {
|
|||||||
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
|
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FIELD_LABELS = {
|
export const DECLINED = 'declined';
|
||||||
COUNTRY: 'country',
|
export const SELF_DESCRIBE = 'self-describe';
|
||||||
};
|
export const OTHER = 'other';
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export const defaultState = {
|
|||||||
verifiedName: null,
|
verifiedName: null,
|
||||||
mostRecentVerifiedName: {},
|
mostRecentVerifiedName: {},
|
||||||
verifiedNameHistory: {},
|
verifiedNameHistory: {},
|
||||||
countriesCodesList: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = (state = defaultState, action = {}) => {
|
const reducer = (state = defaultState, action = {}) => {
|
||||||
@@ -65,7 +64,6 @@ const reducer = (state = defaultState, action = {}) => {
|
|||||||
loaded: true,
|
loaded: true,
|
||||||
loadingError: null,
|
loadingError: null,
|
||||||
verifiedNameHistory: action.payload.verifiedNameHistory,
|
verifiedNameHistory: action.payload.verifiedNameHistory,
|
||||||
countriesCodesList: action.payload.countriesCodesList,
|
|
||||||
};
|
};
|
||||||
case FETCH_SETTINGS.FAILURE:
|
case FETCH_SETTINGS.FAILURE:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function* handleFetchSettings() {
|
|||||||
const { username, userId, roles: userRoles } = getAuthenticatedUser();
|
const { username, userId, roles: userRoles } = getAuthenticatedUser();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
thirdPartyAuthProviders, profileDataManager, timeZones, countries, ...values
|
thirdPartyAuthProviders, profileDataManager, timeZones, ...values
|
||||||
} = yield call(
|
} = yield call(
|
||||||
getSettings,
|
getSettings,
|
||||||
username,
|
username,
|
||||||
@@ -71,7 +71,6 @@ export function* handleFetchSettings() {
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
countriesCodesList: countries,
|
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(fetchSettingsFailure(e.message));
|
yield put(fetchSettingsFailure(e.message));
|
||||||
@@ -84,8 +83,8 @@ export function* handleSaveSettings(action) {
|
|||||||
yield put(saveSettingsBegin());
|
yield put(saveSettingsBegin());
|
||||||
|
|
||||||
const { username, userId } = getAuthenticatedUser();
|
const { username, userId } = getAuthenticatedUser();
|
||||||
const { commitValues, formId, extendedProfile } = action.payload;
|
const { commitValues, formId } = action.payload;
|
||||||
const commitData = Object.keys(extendedProfile).length > 0 ? extendedProfile : { [formId]: commitValues };
|
const commitData = { [formId]: commitValues };
|
||||||
let savedValues = null;
|
let savedValues = null;
|
||||||
if (formId === 'siteLanguage') {
|
if (formId === 'siteLanguage') {
|
||||||
const previousSiteLanguage = getLocale();
|
const previousSiteLanguage = getLocale();
|
||||||
|
|||||||
@@ -88,11 +88,6 @@ const previousSiteLanguageSelector = createSelector(
|
|||||||
accountSettings => accountSettings.previousSiteLanguage,
|
accountSettings => accountSettings.previousSiteLanguage,
|
||||||
);
|
);
|
||||||
|
|
||||||
const countriesSelector = createSelector(
|
|
||||||
accountSettingsSelector,
|
|
||||||
accountSettings => accountSettings.countriesCodesList,
|
|
||||||
);
|
|
||||||
|
|
||||||
const editableFieldErrorSelector = createSelector(
|
const editableFieldErrorSelector = createSelector(
|
||||||
editableFieldNameSelector,
|
editableFieldNameSelector,
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
@@ -111,6 +106,11 @@ const isEditingSelector = createSelector(
|
|||||||
(name, accountSettings) => accountSettings.openFormId === name,
|
(name, accountSettings) => accountSettings.openFormId === name,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const confirmationValuesSelector = createSelector(
|
||||||
|
accountSettingsSelector,
|
||||||
|
accountSettings => accountSettings.confirmationValues,
|
||||||
|
);
|
||||||
|
|
||||||
const errorSelector = createSelector(
|
const errorSelector = createSelector(
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
accountSettings => accountSettings.errors,
|
accountSettings => accountSettings.errors,
|
||||||
@@ -161,7 +161,7 @@ function chooseFormValue(draft, committed) {
|
|||||||
return draft !== undefined ? draft : committed;
|
return draft !== undefined ? draft : committed;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formValuesSelector = createSelector(
|
const formValuesSelector = createSelector(
|
||||||
valuesSelector,
|
valuesSelector,
|
||||||
draftsSelector,
|
draftsSelector,
|
||||||
(values, drafts) => {
|
(values, drafts) => {
|
||||||
@@ -169,20 +169,6 @@ export const formValuesSelector = createSelector(
|
|||||||
Object.entries(values).forEach(([name, value]) => {
|
Object.entries(values).forEach(([name, value]) => {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
formValues[name] = chooseFormValue(drafts[name], value);
|
formValues[name] = chooseFormValue(drafts[name], value);
|
||||||
} else if (typeof value === 'object' && name === 'extended_profile' && value !== null) {
|
|
||||||
const extendedProfile = value.slice();
|
|
||||||
const draftsKeys = Object.keys(drafts);
|
|
||||||
|
|
||||||
if (draftsKeys.length !== 0) {
|
|
||||||
const draftFieldName = draftsKeys[0];
|
|
||||||
const index = extendedProfile.findIndex((profile) => profile.field_name === draftFieldName);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
extendedProfile[index] = { field_name: draftFieldName, field_value: drafts[draftFieldName] };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formValues.extended_profile = [...extendedProfile];
|
|
||||||
} else {
|
} else {
|
||||||
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
||||||
}
|
}
|
||||||
@@ -242,7 +228,6 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
mostRecentApprovedVerifiedNameValueSelector,
|
mostRecentApprovedVerifiedNameValueSelector,
|
||||||
mostRecentVerifiedNameSelector,
|
mostRecentVerifiedNameSelector,
|
||||||
sortedVerifiedNameHistorySelector,
|
sortedVerifiedNameHistorySelector,
|
||||||
countriesSelector,
|
|
||||||
(
|
(
|
||||||
accountSettings,
|
accountSettings,
|
||||||
siteLanguageOptions,
|
siteLanguageOptions,
|
||||||
@@ -260,7 +245,6 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
verifiedName,
|
verifiedName,
|
||||||
mostRecentVerifiedName,
|
mostRecentVerifiedName,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
countriesCodesList,
|
|
||||||
) => ({
|
) => ({
|
||||||
siteLanguageOptions,
|
siteLanguageOptions,
|
||||||
siteLanguage,
|
siteLanguage,
|
||||||
@@ -281,7 +265,6 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
verifiedName,
|
verifiedName,
|
||||||
mostRecentVerifiedName,
|
mostRecentVerifiedName,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
countriesCodesList,
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -306,6 +289,50 @@ export const certPreferenceSelector = createSelector(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const coachingConsentPageSelector = createSelector(
|
||||||
|
accountSettingsSelector,
|
||||||
|
formValuesSelector,
|
||||||
|
activeAccountSelector,
|
||||||
|
profileDataManagerSelector,
|
||||||
|
saveStateSelector,
|
||||||
|
confirmationValuesSelector,
|
||||||
|
errorSelector,
|
||||||
|
(
|
||||||
|
accountSettings,
|
||||||
|
formValues,
|
||||||
|
activeAccount,
|
||||||
|
profileDataManager,
|
||||||
|
saveState,
|
||||||
|
confirmationValues,
|
||||||
|
errors,
|
||||||
|
) => ({
|
||||||
|
loading: accountSettings.loading,
|
||||||
|
loaded: accountSettings.loaded,
|
||||||
|
loadingError: accountSettings.loadingError,
|
||||||
|
isActive: activeAccount,
|
||||||
|
profileDataManager,
|
||||||
|
formValues,
|
||||||
|
saveState,
|
||||||
|
confirmationValues,
|
||||||
|
formErrors: errors,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const demographicsSectionSelector = createSelector(
|
||||||
|
formValuesSelector,
|
||||||
|
draftsSelector,
|
||||||
|
errorSelector,
|
||||||
|
(
|
||||||
|
formValues,
|
||||||
|
drafts,
|
||||||
|
errors,
|
||||||
|
) => ({
|
||||||
|
formValues,
|
||||||
|
drafts,
|
||||||
|
formErrors: errors,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
export const nameChangeSelector = createSelector(
|
export const nameChangeSelector = createSelector(
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
formValuesSelector,
|
formValuesSelector,
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import { profileDataManagerSelector, formValuesSelector } from './selectors';
|
|
||||||
|
|
||||||
const testValue = 'test VALUE';
|
|
||||||
|
|
||||||
describe('profileDataManagerSelector', () => {
|
|
||||||
it('returns the profileDataManager from the state', () => {
|
|
||||||
const state = {
|
|
||||||
accountSettings: {
|
|
||||||
profileDataManager: { testValue },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const result = profileDataManagerSelector(state);
|
|
||||||
|
|
||||||
expect(result).toEqual(state.accountSettings.profileDataManager);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correctly select form values', () => {
|
|
||||||
const state = {
|
|
||||||
accountSettings: {
|
|
||||||
values: {
|
|
||||||
name: 'John Doe',
|
|
||||||
age: 25,
|
|
||||||
},
|
|
||||||
drafts: {
|
|
||||||
age: 26,
|
|
||||||
|
|
||||||
},
|
|
||||||
verifiedNameHistory: 'test',
|
|
||||||
confirmationValues: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = formValuesSelector(state);
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
name: 'John Doe',
|
|
||||||
age: 26,
|
|
||||||
verified_name: '',
|
|
||||||
useVerifiedNameForCerts: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correctly select form values with extended_profile', () => {
|
|
||||||
// Mock data with extended_profile field in both values and drafts
|
|
||||||
const state = {
|
|
||||||
accountSettings: {
|
|
||||||
values: {
|
|
||||||
extended_profile: [
|
|
||||||
{ field_name: 'test_field', field_value: '5' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
drafts: { test_field: '6' },
|
|
||||||
verifiedNameHistory: 'test',
|
|
||||||
confirmationValues: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = formValuesSelector(state);
|
|
||||||
|
|
||||||
const expected = {
|
|
||||||
verified_name: '',
|
|
||||||
useVerifiedNameForCerts: false,
|
|
||||||
extended_profile: [ // Draft value should override the committed value
|
|
||||||
{ field_name: 'test_field', field_value: '6' }, // Value from the committed values
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
|
import pickBy from 'lodash.pickby';
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
|
|
||||||
import { handleRequestError, unpackFieldErrors } from './utils';
|
import { handleRequestError, unpackFieldErrors } from './utils';
|
||||||
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
||||||
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
|
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
|
||||||
import { FIELD_LABELS } from './constants';
|
import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service';
|
||||||
|
import { getDemographics, getDemographicsOptions, patchDemographics } from '../demographics/data/service';
|
||||||
|
import { DEMOGRAPHICS_FIELDS } from '../demographics/data/utils';
|
||||||
|
|
||||||
const SOCIAL_PLATFORMS = [
|
const SOCIAL_PLATFORMS = [
|
||||||
{ id: 'xTwitter', key: 'social_link_x' },
|
{ id: 'twitter', key: 'social_link_twitter' },
|
||||||
{ id: 'facebook', key: 'social_link_facebook' },
|
{ id: 'facebook', key: 'social_link_facebook' },
|
||||||
{ id: 'linkedin', key: 'social_link_linkedin' },
|
{ id: 'linkedin', key: 'social_link_linkedin' },
|
||||||
];
|
];
|
||||||
@@ -153,6 +155,28 @@ export async function getProfileDataManager(username, userRoles) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to determine if the Demographics questions should be displayed to the user. For the
|
||||||
|
* MVP release of Demographics we are limiting the Demographics question visibility only to
|
||||||
|
* MicroBachelors learners.
|
||||||
|
*/
|
||||||
|
export async function shouldDisplayDemographicsQuestions() {
|
||||||
|
const requestUrl = `${getConfig().LMS_BASE_URL}/api/demographics/v1/demographics/status/`;
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
({ data } = await getAuthenticatedHttpClient().get(requestUrl));
|
||||||
|
if (data.display) {
|
||||||
|
return data.display;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// if there was an error then we just hide the section
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getVerifiedName() {
|
export async function getVerifiedName() {
|
||||||
let data;
|
let data;
|
||||||
const client = getAuthenticatedHttpClient();
|
const client = getAuthenticatedHttpClient();
|
||||||
@@ -188,43 +212,31 @@ export async function postVerifiedName(data) {
|
|||||||
.catch(error => handleRequestError(error));
|
.catch(error => handleRequestError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractCountryList(data) {
|
|
||||||
return data?.fields
|
|
||||||
.find(({ name }) => name === FIELD_LABELS.COUNTRY)
|
|
||||||
?.options?.map(({ value }) => (value)) || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCountryList() {
|
|
||||||
const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data } = await getAuthenticatedHttpClient().get(url);
|
|
||||||
return extractCountryList(data);
|
|
||||||
} catch (e) {
|
|
||||||
logError(e);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single function to GET everything considered a setting. Currently encapsulates Account, Preferences, and
|
* A single function to GET everything considered a setting.
|
||||||
* ThirdPartyAuth.
|
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
|
||||||
*/
|
*/
|
||||||
export async function getSettings(username, userRoles) {
|
export async function getSettings(username, userRoles, userId) {
|
||||||
const [
|
const [
|
||||||
account,
|
account,
|
||||||
preferences,
|
preferences,
|
||||||
thirdPartyAuthProviders,
|
thirdPartyAuthProviders,
|
||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
countries,
|
coaching,
|
||||||
|
shouldDisplayDemographicsQuestionsResponse,
|
||||||
|
demographics,
|
||||||
|
demographicsOptions,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getAccount(username),
|
getAccount(username),
|
||||||
getPreferences(username),
|
getPreferences(username),
|
||||||
getThirdPartyAuthProviders(),
|
getThirdPartyAuthProviders(),
|
||||||
getProfileDataManager(username, userRoles),
|
getProfileDataManager(username, userRoles),
|
||||||
getTimeZones(),
|
getTimeZones(),
|
||||||
getCountryList(),
|
getConfig().COACHING_ENABLED && getCoachingPreferences(userId),
|
||||||
|
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && shouldDisplayDemographicsQuestions(),
|
||||||
|
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
|
||||||
|
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographicsOptions(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -233,26 +245,36 @@ export async function getSettings(username, userRoles) {
|
|||||||
thirdPartyAuthProviders,
|
thirdPartyAuthProviders,
|
||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
countries,
|
coaching,
|
||||||
|
shouldDisplayDemographicsSection: shouldDisplayDemographicsQuestionsResponse,
|
||||||
|
...demographics,
|
||||||
|
demographicsOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single function to PATCH everything considered a setting.
|
* A single function to PATCH everything considered a setting.
|
||||||
* Currently encapsulates Account, Preferences, ThirdPartyAuth
|
* Currently encapsulates Account, Preferences, coaching and ThirdPartyAuth
|
||||||
*/
|
*/
|
||||||
export async function patchSettings(username, commitValues) {
|
export async function patchSettings(username, commitValues, userId) {
|
||||||
// Note: time_zone exists in the return value from user/v1/accounts
|
// Note: time_zone exists in the return value from user/v1/accounts
|
||||||
// but it is always null and won't update. It also exists in
|
// but it is always null and won't update. It also exists in
|
||||||
// user/v1/preferences where it does update. This is the one we use.
|
// user/v1/preferences where it does update. This is the one we use.
|
||||||
const preferenceKeys = ['time_zone'];
|
const preferenceKeys = ['time_zone'];
|
||||||
|
const coachingKeys = ['coaching'];
|
||||||
|
const demographicsKeys = DEMOGRAPHICS_FIELDS;
|
||||||
const certificateKeys = ['useVerifiedNameForCerts'];
|
const certificateKeys = ['useVerifiedNameForCerts'];
|
||||||
|
const isDemographicsKey = (value, key) => key.includes('demographics');
|
||||||
const accountCommitValues = omit(
|
const accountCommitValues = omit(
|
||||||
commitValues,
|
commitValues,
|
||||||
preferenceKeys,
|
preferenceKeys,
|
||||||
|
coachingKeys,
|
||||||
|
demographicsKeys,
|
||||||
certificateKeys,
|
certificateKeys,
|
||||||
);
|
);
|
||||||
const preferenceCommitValues = pick(commitValues, preferenceKeys);
|
const preferenceCommitValues = pick(commitValues, preferenceKeys);
|
||||||
|
const coachingCommitValues = pick(commitValues, coachingKeys);
|
||||||
|
const demographicsCommitValues = pickBy(commitValues, isDemographicsKey);
|
||||||
const certCommitValues = pick(commitValues, certificateKeys);
|
const certCommitValues = pick(commitValues, certificateKeys);
|
||||||
const patchRequests = [];
|
const patchRequests = [];
|
||||||
|
|
||||||
@@ -262,6 +284,12 @@ export async function patchSettings(username, commitValues) {
|
|||||||
if (!isEmpty(preferenceCommitValues)) {
|
if (!isEmpty(preferenceCommitValues)) {
|
||||||
patchRequests.push(patchPreferences(username, preferenceCommitValues));
|
patchRequests.push(patchPreferences(username, preferenceCommitValues));
|
||||||
}
|
}
|
||||||
|
if (!isEmpty(coachingCommitValues)) {
|
||||||
|
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
|
||||||
|
}
|
||||||
|
if (!isEmpty(demographicsCommitValues)) {
|
||||||
|
patchRequests.push(patchDemographics(userId, demographicsCommitValues));
|
||||||
|
}
|
||||||
if (!isEmpty(certCommitValues)) {
|
if (!isEmpty(certCommitValues)) {
|
||||||
patchRequests.push(postVerifiedNameConfig(username, certCommitValues));
|
patchRequests.push(postVerifiedNameConfig(username, certCommitValues));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
|
||||||
import { FIELD_LABELS } from './constants';
|
|
||||||
import {
|
|
||||||
getAccount,
|
|
||||||
patchAccount,
|
|
||||||
getPreferences,
|
|
||||||
patchPreferences,
|
|
||||||
getTimeZones,
|
|
||||||
getProfileDataManager,
|
|
||||||
getVerifiedName,
|
|
||||||
getVerifiedNameHistory,
|
|
||||||
postVerifiedName,
|
|
||||||
getCountryList,
|
|
||||||
patchSettings,
|
|
||||||
} from './service';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('@edx/frontend-platform/logging');
|
|
||||||
|
|
||||||
const mockHttpClient = {
|
|
||||||
get: jest.fn(),
|
|
||||||
patch: jest.fn(),
|
|
||||||
post: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue(mockHttpClient);
|
|
||||||
getConfig.mockReturnValue({ LMS_BASE_URL: 'http://lms.test' });
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('account service', () => {
|
|
||||||
describe('getAccount', () => {
|
|
||||||
it('returns unpacked account data', async () => {
|
|
||||||
const apiResponse = {
|
|
||||||
username: 'testuser',
|
|
||||||
social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
|
|
||||||
language_proficiencies: [{ code: 'en' }],
|
|
||||||
};
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: apiResponse });
|
|
||||||
|
|
||||||
const result = await getAccount('testuser');
|
|
||||||
expect(mockHttpClient.get).toHaveBeenCalledWith('http://lms.test/api/user/v1/accounts/testuser');
|
|
||||||
expect(result.social_link_x).toEqual('http://t');
|
|
||||||
expect(result.language_proficiencies).toEqual('en');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('patchAccount', () => {
|
|
||||||
it('sends packed commit data and returns unpacked response', async () => {
|
|
||||||
const commit = { social_link_x: 'http://t' };
|
|
||||||
const apiResponse = {
|
|
||||||
username: 'testuser',
|
|
||||||
social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
|
|
||||||
language_proficiencies: [],
|
|
||||||
};
|
|
||||||
mockHttpClient.patch.mockResolvedValue({ data: apiResponse });
|
|
||||||
|
|
||||||
const result = await patchAccount('testuser', commit);
|
|
||||||
expect(mockHttpClient.patch).toHaveBeenCalledWith(
|
|
||||||
'http://lms.test/api/user/v1/accounts/testuser',
|
|
||||||
expect.objectContaining({ social_links: [{ platform: 'xTwitter', social_link: 'http://t' }] }),
|
|
||||||
expect.any(Object),
|
|
||||||
);
|
|
||||||
expect(result.social_link_x).toEqual('http://t');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getPreferences', () => {
|
|
||||||
it('returns preferences data', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: { theme: 'dark' } });
|
|
||||||
const result = await getPreferences('user');
|
|
||||||
expect(result.theme).toBe('dark');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('patchPreferences', () => {
|
|
||||||
it('patches preferences and returns commitValues', async () => {
|
|
||||||
mockHttpClient.patch.mockResolvedValue({});
|
|
||||||
const commit = { time_zone: 'UTC' };
|
|
||||||
const result = await patchPreferences('user', commit);
|
|
||||||
expect(mockHttpClient.patch).toHaveBeenCalled();
|
|
||||||
expect(result).toEqual(commit);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getTimeZones', () => {
|
|
||||||
it('returns data from API', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: ['UTC', 'PST'] });
|
|
||||||
const result = await getTimeZones('PK');
|
|
||||||
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
|
||||||
'http://lms.test/user_api/v1/preferences/time_zones/',
|
|
||||||
{ params: { country_code: 'PK' } },
|
|
||||||
);
|
|
||||||
expect(result).toEqual(['UTC', 'PST']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getProfileDataManager', () => {
|
|
||||||
it('returns null if no enterprise manages profile', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: { results: [] } });
|
|
||||||
const result = await getProfileDataManager('user', ['learner']);
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns enterprise name if sync is enabled', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: { results: [{ enterprise_customer: { name: 'Acme', sync_learner_profile_data: true } }] } });
|
|
||||||
const result = await getProfileDataManager('user', ['enterprise_learner']);
|
|
||||||
expect(result).toBe('Acme');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getVerifiedName', () => {
|
|
||||||
it('returns verified name data', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: { verified: true } });
|
|
||||||
const result = await getVerifiedName();
|
|
||||||
expect(result.verified).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns {} on error', async () => {
|
|
||||||
mockHttpClient.get.mockRejectedValue(new Error('fail'));
|
|
||||||
const result = await getVerifiedName();
|
|
||||||
expect(result).toEqual({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getVerifiedNameHistory', () => {
|
|
||||||
it('returns verified name history data', async () => {
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: [{ id: 1 }] });
|
|
||||||
const result = await getVerifiedNameHistory();
|
|
||||||
expect(result[0].id).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postVerifiedName', () => {
|
|
||||||
it('posts verified name data', async () => {
|
|
||||||
mockHttpClient.post.mockResolvedValue({});
|
|
||||||
await postVerifiedName({ first_name: 'A' });
|
|
||||||
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
|
||||||
'http://lms.test/api/edx_name_affirmation/v1/verified_name',
|
|
||||||
{ first_name: 'A' },
|
|
||||||
{ headers: { Accept: 'application/json' } },
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getCountryList', () => {
|
|
||||||
it('extracts country values from registration API', async () => {
|
|
||||||
const apiResponse = { fields: [{ name: FIELD_LABELS.COUNTRY, options: [{ value: 'PK' }] }] };
|
|
||||||
mockHttpClient.get.mockResolvedValue({ data: apiResponse });
|
|
||||||
const result = await getCountryList();
|
|
||||||
expect(result).toEqual(['PK']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns [] and logs error on failure', async () => {
|
|
||||||
mockHttpClient.get.mockRejectedValue(new Error('fail'));
|
|
||||||
const result = await getCountryList();
|
|
||||||
expect(result).toEqual([]);
|
|
||||||
expect(logError).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('patchSettings', () => {
|
|
||||||
it('calls patchAccount and patchPreferences as needed', async () => {
|
|
||||||
mockHttpClient.patch.mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
username: 'user',
|
|
||||||
social_links: [],
|
|
||||||
language_proficiencies: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await patchSettings('user', { time_zone: 'UTC', social_link_twitter: 't' });
|
|
||||||
expect(result.username).toBe('user');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { put } from 'redux-saga/effects';
|
import { put } from 'redux-saga/effects';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
|
import { history } from '@edx/frontend-platform';
|
||||||
|
|
||||||
export default function* handleFailure(error, navigate, failureAction = null, failureRedirectPath = null) {
|
export default function* handleFailure(error, failureAction = null, failureRedirectPath = null) {
|
||||||
if (error.fieldErrors && failureAction !== null) {
|
if (error.fieldErrors && failureAction !== null) {
|
||||||
yield put(failureAction({ fieldErrors: error.fieldErrors }));
|
yield put(failureAction({ fieldErrors: error.fieldErrors }));
|
||||||
}
|
}
|
||||||
@@ -10,6 +11,6 @@ export default function* handleFailure(error, navigate, failureAction = null, fa
|
|||||||
yield put(failureAction(error.message));
|
yield put(failureAction(error.message));
|
||||||
}
|
}
|
||||||
if (failureRedirectPath !== null) {
|
if (failureRedirectPath !== null) {
|
||||||
navigate(failureRedirectPath);
|
history.push(failureRedirectPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
@@ -12,8 +13,7 @@ import messages from './messages';
|
|||||||
import Alert from '../Alert';
|
import Alert from '../Alert';
|
||||||
|
|
||||||
const BeforeProceedingBanner = (props) => {
|
const BeforeProceedingBanner = (props) => {
|
||||||
const { instructionMessageId, supportArticleUrl } = props;
|
const { instructionMessageId, intl, supportArticleUrl } = props;
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -25,12 +25,10 @@ const BeforeProceedingBanner = (props) => {
|
|||||||
defaultMessage="Before proceeding, please {actionLink}."
|
defaultMessage="Before proceeding, please {actionLink}."
|
||||||
description="Error that appears if you are trying to delete your account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
description="Error that appears if you are trying to delete your account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
||||||
values={{
|
values={{
|
||||||
actionLink: supportArticleUrl ? (
|
actionLink: (
|
||||||
<Hyperlink destination={supportArticleUrl}>
|
<Hyperlink destination={supportArticleUrl}>
|
||||||
{intl.formatMessage(messages[instructionMessageId])}
|
{intl.formatMessage(messages[instructionMessageId])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
) : (
|
|
||||||
intl.formatMessage(messages[instructionMessageId])
|
|
||||||
),
|
),
|
||||||
siteName: getConfig().SITE_NAME,
|
siteName: getConfig().SITE_NAME,
|
||||||
}}
|
}}
|
||||||
@@ -41,7 +39,8 @@ const BeforeProceedingBanner = (props) => {
|
|||||||
|
|
||||||
BeforeProceedingBanner.propTypes = {
|
BeforeProceedingBanner.propTypes = {
|
||||||
instructionMessageId: PropTypes.string.isRequired,
|
instructionMessageId: PropTypes.string.isRequired,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
supportArticleUrl: PropTypes.string.isRequired,
|
supportArticleUrl: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BeforeProceedingBanner;
|
export default injectIntl(BeforeProceedingBanner);
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import renderer from 'react-test-renderer';
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
jest.mock('react-dom', () => ({
|
|
||||||
...jest.requireActual('react-dom'),
|
|
||||||
createPortal: jest.fn(node => node), // Mock portal behavior
|
|
||||||
}));
|
|
||||||
|
|
||||||
import BeforeProceedingBanner from './BeforeProceedingBanner'; // eslint-disable-line import/first
|
|
||||||
|
|
||||||
describe('BeforeProceedingBanner', () => {
|
|
||||||
it('should match the snapshot if SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT does not have a support link', () => {
|
|
||||||
const props = {
|
|
||||||
instructionMessageId: 'account.settings.delete.account.please.unlink',
|
|
||||||
supportArticleUrl: '',
|
|
||||||
};
|
|
||||||
const tree = renderer
|
|
||||||
.create((
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<BeforeProceedingBanner
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
))
|
|
||||||
.toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match the snapshot when SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT has a support link', () => {
|
|
||||||
const props = {
|
|
||||||
instructionMessageId: 'account.settings.delete.account.please.unlink',
|
|
||||||
supportArticleUrl: 'http://test-support.edx',
|
|
||||||
};
|
|
||||||
const tree = renderer
|
|
||||||
.create((
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<BeforeProceedingBanner
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
))
|
|
||||||
.toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AlertModal,
|
AlertModal,
|
||||||
Button, Form, ActionRow,
|
Button, Input, ValidationFormGroup, ActionRow,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
@@ -78,11 +78,10 @@ export class ConfirmationModal extends Component {
|
|||||||
isOpen={open}
|
isOpen={open}
|
||||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
isOverflowVisible
|
|
||||||
footerNode={(
|
footerNode={(
|
||||||
<ActionRow>
|
<ActionRow>
|
||||||
<Button variant="link" onClick={onCancel}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.cancel'])}</Button>
|
<Button variant="link" onClick={onCancel}>Cancel</Button>
|
||||||
<Button variant="danger" onClick={onSubmit}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.delete'])}</Button>
|
<Button variant="danger" onClick={onSubmit}>Yes, Delete</Button>
|
||||||
</ActionRow>
|
</ActionRow>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -108,26 +107,22 @@ export class ConfirmationModal extends Component {
|
|||||||
<PrintingInstructions />
|
<PrintingInstructions />
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
<Form.Group
|
<ValidationFormGroup
|
||||||
for={passwordFieldId}
|
for={passwordFieldId}
|
||||||
isInvalid={errorType !== null}
|
invalid={errorType !== null}
|
||||||
|
invalidMessage={intl.formatMessage(invalidMessage)}
|
||||||
>
|
>
|
||||||
<Form.Label className="d-block" htmlFor={passwordFieldId}>
|
<label className="d-block" htmlFor={passwordFieldId}>
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
||||||
</Form.Label>
|
</label>
|
||||||
<Form.Control
|
<Input
|
||||||
name="password"
|
name="password"
|
||||||
id={passwordFieldId}
|
id={passwordFieldId}
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
{errorType !== null && (
|
</ValidationFormGroup>
|
||||||
<Form.Control.Feedback type="invalid" feedback-for={passwordFieldId}>
|
|
||||||
{intl.formatMessage(invalidMessage)}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
)}
|
|
||||||
</Form.Group>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||||
jest.mock('react-dom', () => ({
|
ReactDOM.createPortal = node => node;
|
||||||
...jest.requireActual('react-dom'),
|
|
||||||
createPortal: jest.fn(node => node), // Mock portal behavior
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { ConfirmationModal } from './ConfirmationModal'; // eslint-disable-line import/first
|
import { ConfirmationModal } from './ConfirmationModal'; // eslint-disable-line import/first
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Button, Hyperlink } from '@openedx/paragon';
|
import { Button, Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import {
|
import {
|
||||||
@@ -59,7 +59,6 @@ export class DeleteAccount extends React.Component {
|
|||||||
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
||||||
const supportArticleUrl = process.env.SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT;
|
|
||||||
|
|
||||||
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
|
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
|
||||||
// to allow edx.org to fulfill its business requirements.
|
// to allow edx.org to fulfill its business requirements.
|
||||||
@@ -76,74 +75,67 @@ export class DeleteAccount extends React.Component {
|
|||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
||||||
</h2>
|
</h2>
|
||||||
{
|
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
||||||
this.props.canDeleteAccount ? (
|
<p>
|
||||||
<>
|
{intl.formatMessage(
|
||||||
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
messages['account.settings.delete.account.text.1'],
|
||||||
<p>
|
{ siteName: getConfig().SITE_NAME },
|
||||||
{intl.formatMessage(
|
)}
|
||||||
messages['account.settings.delete.account.text.1'],
|
</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p>
|
||||||
)}
|
{intl.formatMessage(
|
||||||
</p>
|
messages[deleteAccountText2MessageKey],
|
||||||
<p>
|
{ siteName: getConfig().SITE_NAME },
|
||||||
{intl.formatMessage(
|
)}
|
||||||
messages[deleteAccountText2MessageKey],
|
</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p>
|
||||||
)}
|
<PrintingInstructions />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p className="text-danger h6">
|
||||||
<PrintingInstructions />
|
{intl.formatMessage(
|
||||||
</p>
|
messages['account.settings.delete.account.text.warning'],
|
||||||
<p className="text-danger h6">
|
{ siteName: getConfig().SITE_NAME },
|
||||||
{intl.formatMessage(
|
)}
|
||||||
messages['account.settings.delete.account.text.warning'],
|
</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p>
|
||||||
)}
|
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">
|
||||||
</p>
|
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
||||||
<p>
|
</Hyperlink>
|
||||||
<Hyperlink destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics">
|
</p>
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
<p>
|
||||||
</Hyperlink>
|
<Button
|
||||||
</p>
|
variant="outline-danger"
|
||||||
<p>
|
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
||||||
<Button
|
disabled={!canDelete}
|
||||||
variant="outline-danger"
|
>
|
||||||
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
||||||
disabled={!canDelete}
|
</Button>
|
||||||
>
|
</p>
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
{isVerifiedAccount ? null : (
|
|
||||||
<BeforeProceedingBanner
|
|
||||||
instructionMessageId={optInInstructionMessageId}
|
|
||||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{hasLinkedTPA ? (
|
|
||||||
<BeforeProceedingBanner
|
|
||||||
instructionMessageId="account.settings.delete.account.please.unlink"
|
|
||||||
supportArticleUrl={supportArticleUrl}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<ConnectedConfirmationModal
|
{isVerifiedAccount ? null : (
|
||||||
status={status}
|
<BeforeProceedingBanner
|
||||||
errorType={errorType}
|
instructionMessageId={optInInstructionMessageId}
|
||||||
onSubmit={this.handleSubmit}
|
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
|
||||||
onCancel={this.handleCancel}
|
/>
|
||||||
onChange={this.handlePasswordChange}
|
)}
|
||||||
password={this.state.password}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConnectedSuccessModal status={status} onClose={this.handleFinalClose} />
|
{hasLinkedTPA ? (
|
||||||
</>
|
<BeforeProceedingBanner
|
||||||
) : (
|
instructionMessageId="account.settings.delete.account.please.unlink"
|
||||||
<p>{intl.formatMessage(messages['account.settings.cannot.delete.account.text'])}</p>
|
supportArticleUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
||||||
)
|
/>
|
||||||
}
|
) : null}
|
||||||
|
|
||||||
|
<ConnectedConfirmationModal
|
||||||
|
status={status}
|
||||||
|
errorType={errorType}
|
||||||
|
onSubmit={this.handleSubmit}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
onChange={this.handlePasswordChange}
|
||||||
|
password={this.state.password}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConnectedSuccessModal status={status} onClose={this.handleFinalClose} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -159,7 +151,6 @@ DeleteAccount.propTypes = {
|
|||||||
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
||||||
hasLinkedTPA: PropTypes.bool,
|
hasLinkedTPA: PropTypes.bool,
|
||||||
isVerifiedAccount: PropTypes.bool,
|
isVerifiedAccount: PropTypes.bool,
|
||||||
canDeleteAccount: PropTypes.bool,
|
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,7 +159,6 @@ DeleteAccount.defaultProps = {
|
|||||||
isVerifiedAccount: true,
|
isVerifiedAccount: true,
|
||||||
status: null,
|
status: null,
|
||||||
errorType: null,
|
errorType: null,
|
||||||
canDeleteAccount: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Assume we're part of the accountSettings state.
|
// Assume we're part of the accountSettings state.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import renderer from 'react-test-renderer';
|
|||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Testing the modals separately, they just clutter up the snapshots if included here.
|
// Testing the modals separately, they just clutter up the snapshots if included here.
|
||||||
jest.mock('./ConfirmationModal', () => function ConfirmationModalMock() {
|
jest.mock('./ConfirmationModal', () => function () {
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
jest.mock('./SuccessModal', () => function SuccessModalMock() {
|
jest.mock('./SuccessModal', () => function () {
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
import React from 'react';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
|
import { Hyperlink } from '@edx/paragon';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const PrintingInstructions = () => {
|
const PrintingInstructions = (props) => {
|
||||||
const intl = useIntl();
|
|
||||||
const actionLink = (
|
const actionLink = (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
// TODO: What would a generic version of this link look like? Should
|
// TODO: What would a generic version of this link look like? Should
|
||||||
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
|
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
|
||||||
// We've removed the link from the default message.
|
// We've removed the link from the default message.
|
||||||
destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UVVOA2/certificates"
|
destination="https://support.edx.org/hc/en-us/sections/115004173027-Receive-and-Share-edX-Certificates"
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
|
{props.intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -40,4 +40,8 @@ const PrintingInstructions = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PrintingInstructions;
|
PrintingInstructions.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default injectIntl(PrintingInstructions);
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { ModalLayer, ModalCloseButton } from '@openedx/paragon';
|
import { ModalLayer, ModalCloseButton } from '@edx/paragon';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const SuccessModal = (props) => {
|
export const SuccessModal = (props) => {
|
||||||
const intl = useIntl();
|
const { status, intl, onClose } = props;
|
||||||
const { status, onClose } = props;
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<ModalLayer isOpen={status === 'deleted'} onClose={onClose}>
|
<ModalLayer isOpen={status === 'deleted'} onClose={onClose}>
|
||||||
@@ -20,7 +20,7 @@ export const SuccessModal = (props) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<ModalCloseButton className="float-right" variant="link">{intl.formatMessage(messages['account.settings.delete.account.modal.after.button'])}</ModalCloseButton>
|
<ModalCloseButton className="float-right" variant="link">Close</ModalCloseButton>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ export const SuccessModal = (props) => {
|
|||||||
|
|
||||||
SuccessModal.propTypes = {
|
SuccessModal.propTypes = {
|
||||||
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
||||||
|
intl: intlShape.isRequired,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,4 +39,4 @@ SuccessModal.defaultProps = {
|
|||||||
status: null,
|
status: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SuccessModal;
|
export default injectIntl(SuccessModal);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { waitFor } from '@testing-library/react';
|
|
||||||
import { SuccessModal } from './SuccessModal';
|
|
||||||
|
|
||||||
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||||
jest.mock('react-dom', () => ({
|
ReactDOM.createPortal = node => node;
|
||||||
...jest.requireActual('react-dom'),
|
|
||||||
createPortal: jest.fn(node => node), // Mock portal behavior
|
import { SuccessModal } from './SuccessModal'; // eslint-disable-line import/first
|
||||||
}));
|
|
||||||
|
const IntlSuccessModal = injectIntl(SuccessModal);
|
||||||
|
|
||||||
describe('SuccessModal', () => {
|
describe('SuccessModal', () => {
|
||||||
let props = {};
|
let props = {};
|
||||||
@@ -19,40 +20,39 @@ describe('SuccessModal', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match default closed success modal snapshot', async () => {
|
it('should match default closed success modal snapshot', () => {
|
||||||
await waitFor(() => {
|
let tree = renderer.create((
|
||||||
const tree = renderer.create((
|
<IntlProvider locale="en"><IntlSuccessModal {...props} /></IntlProvider>))
|
||||||
<IntlProvider locale="en"><SuccessModal {...props} /></IntlProvider>)).toJSON();
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
|
||||||
await waitFor(() => {
|
tree = renderer.create((
|
||||||
const tree = renderer.create((
|
<IntlProvider locale="en"><IntlSuccessModal {...props} status="confirming" /></IntlProvider>))
|
||||||
<IntlProvider locale="en"><SuccessModal {...props} status="confirming" /></IntlProvider>)).toJSON();
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
|
||||||
await waitFor(() => {
|
tree = renderer.create((
|
||||||
const tree = renderer.create((
|
<IntlProvider locale="en"><IntlSuccessModal {...props} status="pending" /></IntlProvider>))
|
||||||
<IntlProvider locale="en"><SuccessModal {...props} status="pending" /></IntlProvider>)).toJSON();
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
|
||||||
await waitFor(() => {
|
tree = renderer.create((
|
||||||
const tree = renderer.create((
|
<IntlProvider locale="en"><IntlSuccessModal {...props} status="failed" /></IntlProvider>))
|
||||||
<IntlProvider locale="en"><SuccessModal {...props} status="failed" /></IntlProvider>)).toJSON();
|
.toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match open success modal snapshot', async () => {
|
it('should match open success modal snapshot', () => {
|
||||||
await waitFor(() => {
|
const tree = renderer
|
||||||
const tree = renderer.create(
|
.create((
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<SuccessModal
|
<IntlSuccessModal
|
||||||
{...props}
|
{...props}
|
||||||
status="deleted"
|
status="deleted" // This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
||||||
/>
|
/>
|
||||||
</IntlProvider>,
|
</IntlProvider>
|
||||||
).toJSON();
|
))
|
||||||
expect(tree).toMatchSnapshot();
|
.toJSON();
|
||||||
});
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`BeforeProceedingBanner should match the snapshot if SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT does not have a support link 1`] = `
|
|
||||||
<div
|
|
||||||
className="alert d-flex align-items-start alert-warning mt-n2"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
|
||||||
data-icon="triangle-exclamation"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Before proceeding, please unlink all social media accounts.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`BeforeProceedingBanner should match the snapshot when SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT has a support link 1`] = `
|
|
||||||
<div
|
|
||||||
className="alert d-flex align-items-start alert-warning mt-n2"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
|
||||||
data-icon="triangle-exclamation"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Before proceeding, please
|
|
||||||
<a
|
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
|
||||||
href="http://test-support.edx"
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
unlink all social media accounts
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `null`;
|
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `null`;
|
||||||
|
|
||||||
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
||||||
[
|
Array [
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -35,14 +35,12 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
data-testid="modal-backdrop"
|
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
onKeyDown={[MockFunction]}
|
onKeyDown={[MockFunction]}
|
||||||
role="presentation"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Are you sure?"
|
aria-label="Are you sure?"
|
||||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__modal-visible-overflow pgn__alert-modal"
|
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -70,19 +68,19 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-circle-exclamation mr-2"
|
className="svg-inline--fa fa-exclamation-circle fa-w-16 mr-2"
|
||||||
data-icon="circle-exclamation"
|
data-icon="exclamation-circle"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,19 +101,19 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
||||||
data-icon="triangle-exclamation"
|
data-icon="exclamation-triangle"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 576 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,57 +130,29 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="pgn__form-group"
|
className="form-group"
|
||||||
for="passwordFieldId"
|
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="pgn__form-label d-block"
|
className="d-block"
|
||||||
htmlFor="form-field3"
|
htmlFor="passwordFieldId"
|
||||||
>
|
>
|
||||||
If you still wish to continue and delete your account, please enter your account password:
|
If you still wish to continue and delete your account, please enter your account password:
|
||||||
</label>
|
</label>
|
||||||
<div
|
<input
|
||||||
className="pgn__form-control-decorator-group"
|
aria-describedby="passwordFieldId-invalid-feedback"
|
||||||
|
className="form-control is-invalid"
|
||||||
|
id="passwordFieldId"
|
||||||
|
name="password"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="password"
|
||||||
|
value="fluffy bunnies"
|
||||||
|
/>
|
||||||
|
<strong
|
||||||
|
className="invalid-feedback"
|
||||||
|
id="passwordFieldId-invalid-feedback"
|
||||||
>
|
>
|
||||||
<input
|
A password is required
|
||||||
aria-describedby="form-field3-5"
|
</strong>
|
||||||
className="has-value form-control is-invalid"
|
|
||||||
id="form-field3"
|
|
||||||
name="password"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
|
||||||
type="password"
|
|
||||||
value="fluffy bunnies"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
|
||||||
feedback-for="passwordFieldId"
|
|
||||||
id="form-field3-5"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="pgn__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden={true}
|
|
||||||
fill="none"
|
|
||||||
focusable={false}
|
|
||||||
height={24}
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width={24}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div>
|
|
||||||
A password is required
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,7 +188,7 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -234,11 +204,11 @@ exports[`ConfirmationModal should match empty password confirmation modal snapsh
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
||||||
[
|
Array [
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -248,17 +218,15 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
/>,
|
/>,
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-layer"
|
className="pgn__modal-layer"
|
||||||
data-focus-lock-disabled={false}
|
data-focus-lock-disabled="disabled"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
onMouseDown={[Function]}
|
|
||||||
onScrollCapture={[Function]}
|
onScrollCapture={[Function]}
|
||||||
onTouchMoveCapture={[Function]}
|
onTouchMoveCapture={[Function]}
|
||||||
onTouchStart={[Function]}
|
|
||||||
onWheelCapture={[Function]}
|
onWheelCapture={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -266,14 +234,12 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
data-testid="modal-backdrop"
|
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
onKeyDown={[MockFunction]}
|
onKeyDown={[MockFunction]}
|
||||||
role="presentation"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Are you sure?"
|
aria-label="Are you sure?"
|
||||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__modal-visible-overflow pgn__alert-modal"
|
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -301,19 +267,19 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
||||||
data-icon="triangle-exclamation"
|
data-icon="exclamation-triangle"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 576 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -330,28 +296,29 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="pgn__form-group"
|
className="form-group"
|
||||||
for="passwordFieldId"
|
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="pgn__form-label d-block"
|
className="d-block"
|
||||||
htmlFor="form-field1"
|
htmlFor="passwordFieldId"
|
||||||
>
|
>
|
||||||
If you still wish to continue and delete your account, please enter your account password:
|
If you still wish to continue and delete your account, please enter your account password:
|
||||||
</label>
|
</label>
|
||||||
<div
|
<input
|
||||||
className="pgn__form-control-decorator-group"
|
aria-describedby=""
|
||||||
|
className="form-control"
|
||||||
|
id="passwordFieldId"
|
||||||
|
name="password"
|
||||||
|
onChange={[MockFunction]}
|
||||||
|
type="password"
|
||||||
|
value="fluffy bunnies"
|
||||||
|
/>
|
||||||
|
<strong
|
||||||
|
className="invalid-feedback"
|
||||||
|
id="passwordFieldId-invalid-feedback"
|
||||||
>
|
>
|
||||||
<input
|
Unable to delete account
|
||||||
className="has-value form-control"
|
</strong>
|
||||||
id="form-field1"
|
|
||||||
name="password"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
|
||||||
type="password"
|
|
||||||
value="fluffy bunnies"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -387,7 +354,7 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -397,7 +364,7 @@ exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||||
|
onClick={[Function]}
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -73,7 +74,8 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||||
|
onClick={[Function]}
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -95,19 +97,19 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
||||||
data-icon="triangle-exclamation"
|
data-icon="exclamation-triangle"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 576 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +117,8 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
Before proceeding, please
|
Before proceeding, please
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
|
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
|
||||||
|
onClick={[Function]}
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
activate your account
|
activate your account
|
||||||
@@ -153,7 +156,8 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||||
|
onClick={[Function]}
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -175,19 +179,19 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
||||||
data-icon="triangle-exclamation"
|
data-icon="exclamation-triangle"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 576 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={{}}
|
style={Object {}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -195,7 +199,8 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
Before proceeding, please
|
Before proceeding, please
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account"
|
href="https://support.edx.org/hc/en-us/articles/207206067"
|
||||||
|
onClick={[Function]}
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
unlink all social media accounts
|
unlink all social media accounts
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
|||||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `null`;
|
exports[`SuccessModal should match default closed success modal snapshot 4`] = `null`;
|
||||||
|
|
||||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||||
[
|
Array [
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -23,17 +23,15 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
/>,
|
/>,
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-layer"
|
className="pgn__modal-layer"
|
||||||
data-focus-lock-disabled={false}
|
data-focus-lock-disabled="disabled"
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
onMouseDown={[Function]}
|
|
||||||
onScrollCapture={[Function]}
|
onScrollCapture={[Function]}
|
||||||
onTouchMoveCapture={[Function]}
|
onTouchMoveCapture={[Function]}
|
||||||
onTouchStart={[Function]}
|
|
||||||
onWheelCapture={[Function]}
|
onWheelCapture={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -41,10 +39,8 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
data-testid="modal-backdrop"
|
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
onKeyDown={[MockFunction]}
|
onKeyDown={[MockFunction]}
|
||||||
role="presentation"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="mw-sm p-5 bg-white mx-auto my-3"
|
className="mw-sm p-5 bg-white mx-auto my-3"
|
||||||
@@ -77,7 +73,7 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
{
|
Object {
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -87,7 +83,7 @@ exports[`SuccessModal should match open success modal snapshot 1`] = `
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={0}
|
tabIndex={-1}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import formurlencoded from 'form-urlencoded';
|
|
||||||
import { handleRequestError } from '../../data/utils';
|
|
||||||
|
|
||||||
import { postDeleteAccount } from './service';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('form-urlencoded');
|
|
||||||
jest.mock('../../data/utils');
|
|
||||||
|
|
||||||
describe('postDeleteAccount', () => {
|
|
||||||
const mockPost = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({
|
|
||||||
LMS_BASE_URL: 'http://testserver',
|
|
||||||
});
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({
|
|
||||||
post: mockPost,
|
|
||||||
});
|
|
||||||
|
|
||||||
formurlencoded.mockImplementation(obj => `encoded:${JSON.stringify(obj)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('posts delete account request with password', async () => {
|
|
||||||
const mockResponse = { data: { success: true } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const result = await postDeleteAccount('mypassword');
|
|
||||||
|
|
||||||
expect(getConfig).toHaveBeenCalled();
|
|
||||||
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
|
||||||
expect(formurlencoded).toHaveBeenCalledWith({ password: 'mypassword' });
|
|
||||||
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/api/user/v1/accounts/deactivate_logout/',
|
|
||||||
'encoded:{"password":"mypassword"}',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(mockResponse.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls handleRequestError and throws when request fails', async () => {
|
|
||||||
const mockError = new Error('Request failed');
|
|
||||||
mockPost.mockRejectedValueOnce(mockError);
|
|
||||||
|
|
||||||
handleRequestError.mockImplementation(() => {
|
|
||||||
throw mockError;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(postDeleteAccount('wrongpassword')).rejects.toThrow('Request failed');
|
|
||||||
|
|
||||||
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
'account.settings.cannot.delete.account.text': {
|
|
||||||
id: 'account.settings.cannot.delete.account.text',
|
|
||||||
defaultMessage: 'Please note that, for legal and regulatory compliance purposes, account deletion is currently unavailable.',
|
|
||||||
description: 'This text is visible when user is not allowed to delete account',
|
|
||||||
},
|
|
||||||
'account.settings.delete.account.header': {
|
'account.settings.delete.account.header': {
|
||||||
id: 'account.settings.delete.account.header',
|
id: 'account.settings.delete.account.header',
|
||||||
defaultMessage: 'Delete My Account',
|
defaultMessage: 'Delete My Account',
|
||||||
|
|||||||
80
src/account-settings/demographics/Checkboxes.jsx
Normal file
80
src/account-settings/demographics/Checkboxes.jsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Form } from '@edx/paragon';
|
||||||
|
import { DECLINED } from '../data/constants';
|
||||||
|
|
||||||
|
const Checkboxes = (props) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
options,
|
||||||
|
values,
|
||||||
|
onChange,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState(values);
|
||||||
|
useEffect(() => {
|
||||||
|
onChange(id, selected);
|
||||||
|
}, [id, onChange, selected]);
|
||||||
|
|
||||||
|
const handleToggle = (value, option) => {
|
||||||
|
// If the user checked 'declined', uncheck all other options
|
||||||
|
if (value && option === DECLINED) {
|
||||||
|
setSelected([DECLINED]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If option checked, make sure this option is in `selected` (and remove 'declined')
|
||||||
|
if (value && !selected.includes(option)) {
|
||||||
|
const newSelected = selected.filter(i => i !== DECLINED).concat(option);
|
||||||
|
setSelected(newSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unchecked, make sure this option is NOT in `selected`
|
||||||
|
if (!value) {
|
||||||
|
setSelected(selected.filter(i => i !== option));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCheckboxes = () => options.map((option, index) => {
|
||||||
|
const isFirst = index === 0;
|
||||||
|
const isChecked = selected.includes(option.value);
|
||||||
|
return (
|
||||||
|
<div key={option.value} className="checkboxOption">
|
||||||
|
<Form.Checkbox
|
||||||
|
type="checkbox"
|
||||||
|
id={option.value}
|
||||||
|
name={option.value}
|
||||||
|
value={option.value}
|
||||||
|
checked={isChecked}
|
||||||
|
autoFocus={isFirst}
|
||||||
|
onChange={(event) => handleToggle(event.target.checked, option.value)}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Form.Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div role="group">
|
||||||
|
{renderCheckboxes()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkboxes.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
options: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
value: PropTypes.string,
|
||||||
|
label: PropTypes.string,
|
||||||
|
})),
|
||||||
|
values: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
Checkboxes.defaultProps = {
|
||||||
|
options: [],
|
||||||
|
values: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Checkboxes;
|
||||||
357
src/account-settings/demographics/DemographicsSection.jsx
Normal file
357
src/account-settings/demographics/DemographicsSection.jsx
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import {
|
||||||
|
FormattedMessage,
|
||||||
|
injectIntl,
|
||||||
|
intlShape,
|
||||||
|
} from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import { Hyperlink, Form } from '@edx/paragon';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
import memoize from 'memoize-one';
|
||||||
|
import { demographicsSectionSelector } from '../data/selectors';
|
||||||
|
import EditableSelectField from '../EditableSelectField';
|
||||||
|
import Checkboxes from './Checkboxes';
|
||||||
|
import Alert from '../Alert';
|
||||||
|
import { saveMultipleSettings, updateDraft } from '../data/actions';
|
||||||
|
import {
|
||||||
|
OTHER,
|
||||||
|
SELF_DESCRIBE,
|
||||||
|
} from '../data/constants';
|
||||||
|
import messages from './DemographicsSection.messages';
|
||||||
|
|
||||||
|
class DemographicsSection extends React.Component {
|
||||||
|
// We check the `demographicsOptions` prop to see if it is empty before we attempt to extract and
|
||||||
|
// format the available options for each question from the API response.
|
||||||
|
getApiOptions = memoize((demographicsOptions) => (this.hasRetrievedDemographicsOptions() && {
|
||||||
|
demographicsGenderOptions: this.addDefaultOption('account.settings.field.demographics.gender.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.gender.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
/* Ethnicity options don't need the blank/default option */
|
||||||
|
demographicsEthnicityOptions: demographicsOptions.actions.POST.user_ethnicity.child.children.ethnicity.choices.map(
|
||||||
|
key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
demographicsIncomeOptions: this.addDefaultOption('account.settings.field.demographics.income.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.income.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
demographicsMilitaryHistoryOptions: this.addDefaultOption('account.settings.field.demographics.military_history.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.military_history.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
demographicsEducationLevelOptions: this.addDefaultOption('account.settings.field.demographics.education_level.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.learner_education_level.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
demographicsWorkStatusOptions: this.addDefaultOption('account.settings.field.demographics.work_status.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.work_status.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
demographicsWorkSectorOptions: this.addDefaultOption('account.settings.field.demographics.work_sector.options.empty')
|
||||||
|
.concat(demographicsOptions.actions.POST.current_work_sector.choices.map(key => ({
|
||||||
|
value: key.value,
|
||||||
|
label: key.display_name,
|
||||||
|
}))),
|
||||||
|
}));
|
||||||
|
|
||||||
|
ethnicityFieldDisplay = (demographicsEthnicityOptions) => {
|
||||||
|
let ethnicities = [];
|
||||||
|
if (get(this, 'props.formValues.demographics_user_ethnicity')) {
|
||||||
|
ethnicities = this.props.formValues.demographics_user_ethnicity;
|
||||||
|
}
|
||||||
|
return ethnicities.map((e) => {
|
||||||
|
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
|
||||||
|
return matchingOption && matchingOption.label;
|
||||||
|
}).join(', ');
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEditableFieldChange = (name, value) => {
|
||||||
|
this.props.updateDraft(name, value);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSubmit = (formId) => {
|
||||||
|
// We have some custom fields in this section. Instead of relying on the
|
||||||
|
// submitted values, submit the values stored in 'drafts'.
|
||||||
|
const { drafts } = this.props;
|
||||||
|
const settingsArray = Object.entries(drafts).map(([field, value]) => ({
|
||||||
|
formId: field,
|
||||||
|
commitValues: value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.props.saveMultipleSettings(settingsArray, formId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method that adds the specified message as a default option to the list of available
|
||||||
|
* choices.
|
||||||
|
*
|
||||||
|
* @param {*} messageId id of message matching desired default label text
|
||||||
|
*/
|
||||||
|
addDefaultOption(messageId) {
|
||||||
|
return [{
|
||||||
|
value: '',
|
||||||
|
label: this.props.intl.formatMessage(messages[messageId]),
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method that helps determine if we were able to retrieve the available options for
|
||||||
|
* the Demographics questions. Returns true if the `demographicsOptions` prop is _not_ empty,
|
||||||
|
* otherwise false. This prop being empty is indicative of a failure communicating with the
|
||||||
|
* Demographics IDA's API.
|
||||||
|
*/
|
||||||
|
hasRetrievedDemographicsOptions() {
|
||||||
|
return !isEmpty(this.props.formValues.demographicsOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If an error is encountered when trying to communicate with the Demographics IDA then we will
|
||||||
|
* display an Alert letting the user know that their info will not be displayed and temporarily
|
||||||
|
* cannot be updated.
|
||||||
|
*/
|
||||||
|
renderDemographicsServiceIssueWarning() {
|
||||||
|
if (!isEmpty(this.props.formErrors.demographicsError)
|
||||||
|
|| !this.hasRetrievedDemographicsOptions()) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
tabIndex="-1"
|
||||||
|
ref={this.alertRef}
|
||||||
|
>
|
||||||
|
<Alert className="alert alert-danger" role="alert">
|
||||||
|
<FormattedMessage
|
||||||
|
id="account.settings.message.demographics.service.issue"
|
||||||
|
defaultMessage="An error occurred attempting to retrieve or save your account information. Please try again later."
|
||||||
|
description="alert message informing the user that the there is a problem retrieving or updating information from the Demographics microservice"
|
||||||
|
/>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const editableFieldProps = {
|
||||||
|
onChange: this.handleEditableFieldChange,
|
||||||
|
onSubmit: this.handleSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
demographicsGenderOptions,
|
||||||
|
demographicsEthnicityOptions,
|
||||||
|
demographicsIncomeOptions,
|
||||||
|
demographicsMilitaryHistoryOptions,
|
||||||
|
demographicsEducationLevelOptions,
|
||||||
|
demographicsWorkStatusOptions,
|
||||||
|
demographicsWorkSectorOptions,
|
||||||
|
} = this.getApiOptions(this.props.formValues.demographicsOptions);
|
||||||
|
|
||||||
|
const showSelfDescribe = this.props.formValues.demographics_gender === SELF_DESCRIBE;
|
||||||
|
const showWorkStatusDescribe = this.props.formValues.demographics_work_status === OTHER;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="account-section pt-3 mb-5" id="demographics-information" ref={this.props.forwardRef}>
|
||||||
|
<h2 className="section-heading h4 mb-3">
|
||||||
|
{this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<Hyperlink
|
||||||
|
destination={`${getConfig().MARKETING_SITE_BASE_URL}/demographics`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{this.props.intl.formatMessage(
|
||||||
|
messages['account.settings.section.demographics.why'],
|
||||||
|
{
|
||||||
|
siteName: getConfig().SITE_NAME,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</Hyperlink>
|
||||||
|
</p>
|
||||||
|
{this.renderDemographicsServiceIssueWarning()}
|
||||||
|
{/*
|
||||||
|
If the demographicsOptions props are empty then there is no need to display the fields as
|
||||||
|
the user will not have any choices available to select, nor will they be able to update
|
||||||
|
their answers.
|
||||||
|
*/}
|
||||||
|
{this.hasRetrievedDemographicsOptions() && (
|
||||||
|
<div id="demographics-fields">
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_gender"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_gender}
|
||||||
|
userSuppliedValue={showSelfDescribe ? this.props.formValues.demographics_gender_description : null}
|
||||||
|
options={demographicsGenderOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
>
|
||||||
|
{showSelfDescribe && (
|
||||||
|
<Form.Control
|
||||||
|
name="demographics_gender_description"
|
||||||
|
id="field-demographics_gender_description"
|
||||||
|
type="text"
|
||||||
|
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description.empty'])}
|
||||||
|
value={this.props.formValues.demographics_gender_description}
|
||||||
|
onChange={(e) => this.handleEditableFieldChange('demographics_gender_description', e.target.value)}
|
||||||
|
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description'])}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</EditableSelectField>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_user_ethnicity"
|
||||||
|
type="select"
|
||||||
|
hidden
|
||||||
|
value={this.ethnicityFieldDisplay(demographicsEthnicityOptions)}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
>
|
||||||
|
<Checkboxes
|
||||||
|
id="demographics_user_ethnicity"
|
||||||
|
options={demographicsEthnicityOptions}
|
||||||
|
values={this.props.formValues.demographics_user_ethnicity}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
</EditableSelectField>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_income"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_income}
|
||||||
|
options={demographicsIncomeOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.income'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.income.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_military_history"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_military_history}
|
||||||
|
options={demographicsMilitaryHistoryOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_learner_education_level"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_learner_education_level}
|
||||||
|
options={demographicsEducationLevelOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_parent_education_level"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_parent_education_level}
|
||||||
|
options={demographicsEducationLevelOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_work_status"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_work_status}
|
||||||
|
userSuppliedValue={showWorkStatusDescribe
|
||||||
|
? this.props.formValues.demographics_work_status_description
|
||||||
|
: null}
|
||||||
|
options={demographicsWorkStatusOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
>
|
||||||
|
{showWorkStatusDescribe && (
|
||||||
|
<Form.Control
|
||||||
|
name="demographics_work_status_description"
|
||||||
|
id="field-demographics_work_status_description"
|
||||||
|
type="text"
|
||||||
|
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description.empty'])}
|
||||||
|
value={this.props.formValues.demographics_work_status_description}
|
||||||
|
onChange={(e) => this.handleEditableFieldChange('demographics_work_status_description', e.target.value)}
|
||||||
|
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description'])}
|
||||||
|
className="mt-1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</EditableSelectField>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_current_work_sector"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_current_work_sector}
|
||||||
|
options={demographicsWorkSectorOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
<EditableSelectField
|
||||||
|
name="demographics_future_work_sector"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues.demographics_future_work_sector}
|
||||||
|
options={demographicsWorkSectorOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DemographicsSection.propTypes = {
|
||||||
|
intl: intlShape.isRequired,
|
||||||
|
formValues: PropTypes.shape({
|
||||||
|
demographics_gender: PropTypes.string,
|
||||||
|
demographics_user_ethnicity: PropTypes.shape([]),
|
||||||
|
demographics_income: PropTypes.string,
|
||||||
|
demographics_military_history: PropTypes.string,
|
||||||
|
demographics_learner_education_level: PropTypes.string,
|
||||||
|
demographics_parent_education_level: PropTypes.string,
|
||||||
|
demographics_work_status: PropTypes.string,
|
||||||
|
demographics_current_work_sector: PropTypes.string,
|
||||||
|
demographics_future_work_sector: PropTypes.string,
|
||||||
|
demographics_work_status_description: PropTypes.string,
|
||||||
|
demographics_gender_description: PropTypes.string,
|
||||||
|
demographicsOptions: PropTypes.shape({}),
|
||||||
|
}).isRequired,
|
||||||
|
drafts: PropTypes.shape({
|
||||||
|
demographics_gender: PropTypes.string,
|
||||||
|
demographics_user_ethnicity: PropTypes.shape([]),
|
||||||
|
demographics_income: PropTypes.string,
|
||||||
|
demographics_military_history: PropTypes.string,
|
||||||
|
demographics_learner_education_level: PropTypes.string,
|
||||||
|
demographics_parent_education_level: PropTypes.string,
|
||||||
|
demographics_work_status: PropTypes.string,
|
||||||
|
demographics_current_work_sector: PropTypes.string,
|
||||||
|
demographics_future_work_sector: PropTypes.string,
|
||||||
|
demographics_work_status_description: PropTypes.string,
|
||||||
|
demographics_gender_description: PropTypes.string,
|
||||||
|
demographicsOptions: PropTypes.shape({}),
|
||||||
|
}).isRequired,
|
||||||
|
formErrors: PropTypes.shape({
|
||||||
|
demographicsError: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
forwardRef: PropTypes.func.isRequired,
|
||||||
|
updateDraft: PropTypes.func.isRequired,
|
||||||
|
saveMultipleSettings: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(demographicsSectionSelector, {
|
||||||
|
saveMultipleSettings,
|
||||||
|
updateDraft,
|
||||||
|
})(injectIntl(DemographicsSection));
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
/* Demographics section heading */
|
||||||
|
'account.settings.section.demographics.information': {
|
||||||
|
id: 'account.settings.section.demographics.information',
|
||||||
|
defaultMessage: 'Optional Information',
|
||||||
|
description: 'The optional information section heading.',
|
||||||
|
},
|
||||||
|
/* Gender identity */
|
||||||
|
'account.settings.field.demographics.gender': {
|
||||||
|
id: 'account.settings.field.demographics.gender',
|
||||||
|
defaultMessage: 'Gender identity',
|
||||||
|
description: 'Label for account settings gender identity field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.gender.empty': {
|
||||||
|
id: 'account.settings.field.demographics.gender.empty',
|
||||||
|
defaultMessage: 'Add gender identity',
|
||||||
|
description: 'Placeholder for empty account settings gender identity field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.gender.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.gender.options.empty',
|
||||||
|
defaultMessage: 'Select a gender identity',
|
||||||
|
description: 'Placeholder for the gender identity options dropdown.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.gender_description': {
|
||||||
|
id: 'account.settings.field.demographics.gender_description',
|
||||||
|
defaultMessage: 'Gender identity description',
|
||||||
|
description: 'Label for account settings gender identity description field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.gender_description.empty': {
|
||||||
|
id: 'account.settings.field.demographics.gender_description.empty',
|
||||||
|
defaultMessage: 'Enter description',
|
||||||
|
description: 'Placeholder for empty account settings gender identity field.',
|
||||||
|
},
|
||||||
|
/* Ethnicity */
|
||||||
|
'account.settings.field.demographics.ethnicity': {
|
||||||
|
id: 'account.settings.field.demographics.ethnicity',
|
||||||
|
defaultMessage: 'Race/Ethnicity identity',
|
||||||
|
description: 'Label for account settings ethnic background field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.ethnicity.empty': {
|
||||||
|
id: 'account.settings.field.demographics.ethnicity.empty',
|
||||||
|
defaultMessage: 'Add race/ethnicity identity',
|
||||||
|
description: 'Placeholder for empty account settings ethnic background field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.ethnicity.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.ethnicity.options.empty',
|
||||||
|
defaultMessage: 'Select all that apply', // TODO: Is this the desired text?
|
||||||
|
description: 'Placeholder for the ethnic background options field.',
|
||||||
|
},
|
||||||
|
/* Income */
|
||||||
|
'account.settings.field.demographics.income': {
|
||||||
|
id: 'account.settings.field.demographics.income',
|
||||||
|
defaultMessage: 'Family income',
|
||||||
|
description: 'Label for account settings household income field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.income.empty': {
|
||||||
|
id: 'account.settings.field.demographics.income.empty',
|
||||||
|
defaultMessage: 'Add family income',
|
||||||
|
description: 'Placeholder for empty account settings household income field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.income.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.income.options.empty',
|
||||||
|
defaultMessage: 'Select a family income range',
|
||||||
|
description: 'Placeholder for the household income dropdown.',
|
||||||
|
},
|
||||||
|
/* Military history */
|
||||||
|
'account.settings.field.demographics.military_history': {
|
||||||
|
id: 'account.settings.field.demographics.military_history',
|
||||||
|
defaultMessage: 'U.S. Military status',
|
||||||
|
description: 'Label for account settings military history field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.military_history.empty': {
|
||||||
|
id: 'account.settings.field.demographics.military_history.empty',
|
||||||
|
defaultMessage: 'Add military status',
|
||||||
|
description: 'Placeholder for empty account settings military history field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.military_history.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.military_history.options.empty',
|
||||||
|
defaultMessage: 'Select military status',
|
||||||
|
description: 'Placeholder for the military history dropdown.',
|
||||||
|
},
|
||||||
|
/* Learner and family education level */
|
||||||
|
'account.settings.field.demographics.learner_education_level': {
|
||||||
|
id: 'account.settings.field.demographics.learner_education_level',
|
||||||
|
defaultMessage: 'Your education level',
|
||||||
|
description: 'Label for account settings learner education level field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.learner_education_level.empty': {
|
||||||
|
id: 'account.settings.field.demographics.learner_education_level.empty',
|
||||||
|
defaultMessage: 'Add education level',
|
||||||
|
description: 'Placeholder for empty account settings learner education level field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.parent_education_level': {
|
||||||
|
id: 'account.settings.field.demographics.parent_education_level',
|
||||||
|
defaultMessage: 'Parents/Guardians education level',
|
||||||
|
description: 'Label for account settings parent education level field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.parent_education_level.empty': {
|
||||||
|
id: 'account.settings.field.demographics.parent_education_level.empty',
|
||||||
|
defaultMessage: 'Add education level',
|
||||||
|
description: 'Placeholder for empty account settings parent education level field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.education_level.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.education_level.options.empty',
|
||||||
|
defaultMessage: 'Select education level',
|
||||||
|
description: 'Placeholder for the education level options dropdown.',
|
||||||
|
},
|
||||||
|
/* Work status */
|
||||||
|
'account.settings.field.demographics.work_status': {
|
||||||
|
id: 'account.settings.field.demographics.work_status',
|
||||||
|
defaultMessage: 'Employment status',
|
||||||
|
description: 'Label for account settings work status field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.work_status.empty': {
|
||||||
|
id: 'account.settings.field.demographics.work_status.empty',
|
||||||
|
defaultMessage: 'Add employment status',
|
||||||
|
description: 'Placeholder for empty account settings work status field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.work_status.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.work_status.options.empty',
|
||||||
|
defaultMessage: 'Select employment status',
|
||||||
|
description: 'Placeholder for the work status options dropdown.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.work_status_description': {
|
||||||
|
id: 'account.settings.field.demographics.work_status_description',
|
||||||
|
defaultMessage: 'Employment status description',
|
||||||
|
description: 'Label for account settings work status description field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.work_status_description.empty': {
|
||||||
|
id: 'account.settings.field.demographics.work_status_description.empty',
|
||||||
|
defaultMessage: 'Enter description',
|
||||||
|
description: 'Placeholder for empty account settings work status description field.',
|
||||||
|
},
|
||||||
|
/* Work sector */
|
||||||
|
'account.settings.field.demographics.current_work_sector': {
|
||||||
|
id: 'account.settings.field.demographics.current_work_sector',
|
||||||
|
defaultMessage: 'Current work industry',
|
||||||
|
description: 'Label for account settings current work sector field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.current_work_sector.empty': {
|
||||||
|
id: 'account.settings.field.demographics.current_work_sector.empty',
|
||||||
|
defaultMessage: 'Add work industry',
|
||||||
|
description: 'Placeholder for empty account settings current work sector field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.future_work_sector': {
|
||||||
|
id: 'account.settings.field.demographics.future_work_sector',
|
||||||
|
defaultMessage: 'Future work industry',
|
||||||
|
description: 'Label for account settings future work sector field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.future_work_sector.empty': {
|
||||||
|
id: 'account.settings.field.demographics.future_work_sector.empty',
|
||||||
|
defaultMessage: 'Add work industry',
|
||||||
|
description: 'Placeholder for empty account settings future work sector field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.demographics.work_sector.options.empty': {
|
||||||
|
id: 'account.settings.field.demographics.work_sector.options.empty',
|
||||||
|
defaultMessage: 'Select work industry',
|
||||||
|
description: 'Placeholder for the work sector options dropdown.',
|
||||||
|
},
|
||||||
|
/* Legal copy link text */
|
||||||
|
'account.settings.section.demographics.why': {
|
||||||
|
id: 'account.settings.section.demographics.why',
|
||||||
|
defaultMessage: 'Why does {siteName} collect this information?',
|
||||||
|
description: 'Link text for a link to external legal text',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
140
src/account-settings/demographics/data/service.js
Normal file
140
src/account-settings/demographics/data/service.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
import { convertData, TO, FROM } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method that attempts to extract errors from the response of a PATCH request in order to
|
||||||
|
* display a warning or otherwise meaningful message to the user.
|
||||||
|
*
|
||||||
|
* @param {Error} error
|
||||||
|
*/
|
||||||
|
export function createDemographicsError(error) {
|
||||||
|
const apiError = Object.create(error);
|
||||||
|
// If the error received has the `httpResponseData` field in it, then we should have reason to
|
||||||
|
// believe the Demographics service is alive and responding. Extract errors from fields where
|
||||||
|
// appropriate so we can display them to the user.
|
||||||
|
if (get(error, 'customAttributes.httpErrorResponseData')) {
|
||||||
|
apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
||||||
|
if (get(apiError, 'fieldErrors.gender_description')) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
apiError.fieldErrors.demographics_gender = apiError.fieldErrors.gender_description[0];
|
||||||
|
delete apiError.fieldErrors.gender_description;
|
||||||
|
} else if (get(apiError, 'fieldErrors.work_status_description')) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
apiError.fieldErrors.demographics_work_status = apiError.fieldErrors.work_status_description[0];
|
||||||
|
delete apiError.fieldErrors.work_status_description;
|
||||||
|
}
|
||||||
|
// Otherwise, when the service is down, the error response will not contain a
|
||||||
|
// `httpErrorResponseData` field. Add a generic 'demographicsError' field to the fieldErrors that
|
||||||
|
// will trigger showing an Alert to the user to them them know the update was unsuccessful.
|
||||||
|
} else {
|
||||||
|
apiError.fieldErrors = {
|
||||||
|
demographicsError: error.customAttributes.httpErrorType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* post all of the data related to demographics.
|
||||||
|
* @param {Number} userId users are identified in the api by LMS id
|
||||||
|
* @param {Object} commitValues { demographics }
|
||||||
|
*/
|
||||||
|
export async function postDemographics(userId) {
|
||||||
|
const requestConfig = { headers: { 'Content-Type': 'application/json' } };
|
||||||
|
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/`;
|
||||||
|
const commitValues = { user: userId };
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
({ data } = await getAuthenticatedHttpClient()
|
||||||
|
.post(requestUrl, commitValues, requestConfig)
|
||||||
|
.catch((error) => {
|
||||||
|
const apiError = createDemographicsError(error);
|
||||||
|
throw apiError;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return convertData(data, FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get all data related to the demographics.
|
||||||
|
* @param {Number} userId users are identified in the api by LMS id
|
||||||
|
*/
|
||||||
|
export async function getDemographics(userId) {
|
||||||
|
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
({ data } = await getAuthenticatedHttpClient()
|
||||||
|
.get(requestUrl));
|
||||||
|
|
||||||
|
data = convertData(data, FROM);
|
||||||
|
} catch (error) {
|
||||||
|
const apiError = Object.create(error);
|
||||||
|
// if the API called resulted in this user receiving a 404 then follow up with a POST call to
|
||||||
|
// try and create the demographics entity on the backend
|
||||||
|
if (apiError.customAttributes.httpErrorStatus) {
|
||||||
|
if (apiError.customAttributes.httpErrorStatus === 404) {
|
||||||
|
data = await postDemographics(userId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = {
|
||||||
|
user: userId,
|
||||||
|
demographics_gender: '',
|
||||||
|
demographics_gender_description: '',
|
||||||
|
demographics_income: '',
|
||||||
|
demographics_learner_education_level: '',
|
||||||
|
demographics_parent_education_level: '',
|
||||||
|
demographics_military_history: '',
|
||||||
|
demographics_work_status: '',
|
||||||
|
demographics_work_status_description: '',
|
||||||
|
demographics_current_work_sector: '',
|
||||||
|
demographics_future_work_sector: '',
|
||||||
|
demographics_user_ethnicity: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* patch all of the data related to demographics.
|
||||||
|
* @param {Number} userId users are identified in the api by LMS id
|
||||||
|
* @param {Object} commitValues { demographics }
|
||||||
|
*/
|
||||||
|
export async function patchDemographics(userId, commitValues) {
|
||||||
|
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
|
||||||
|
const convertedCommitValues = convertData(commitValues, TO);
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
({ data } = await getAuthenticatedHttpClient()
|
||||||
|
.patch(requestUrl, convertedCommitValues)
|
||||||
|
.catch((error) => {
|
||||||
|
const apiError = createDemographicsError(error);
|
||||||
|
throw apiError;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return convertData(data, FROM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retrieve the options for each field from the Demographics API
|
||||||
|
*/
|
||||||
|
export async function getDemographicsOptions() {
|
||||||
|
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/`;
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
({ data } = await getAuthenticatedHttpClient().options(requestUrl));
|
||||||
|
} catch (error) {
|
||||||
|
// We are catching and suppressing errors here on purpose. If an error occurs during the
|
||||||
|
// getDemographicsOptions call we will pass back an empty `data` object. Downstream we make
|
||||||
|
// the assumption that if the demographicsOptions object is empty that there was an issue or
|
||||||
|
// error communicating with the service/API.
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
63
src/account-settings/demographics/data/utils.js
Normal file
63
src/account-settings/demographics/data/utils.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
export const TO = 'to';
|
||||||
|
export const FROM = 'from';
|
||||||
|
export const DEMOGRAPHICS_FIELDS = [
|
||||||
|
'demographics_gender',
|
||||||
|
'demographics_gender_description',
|
||||||
|
'demographics_income',
|
||||||
|
'demographics_learner_education_level',
|
||||||
|
'demographics_parent_education_level',
|
||||||
|
'demographics_military_history',
|
||||||
|
'demographics_work_status',
|
||||||
|
'demographics_work_status_description',
|
||||||
|
'demographics_current_work_sector',
|
||||||
|
'demographics_future_work_sector',
|
||||||
|
'demographics_user_ethnicity',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Frontend wants (example):
|
||||||
|
// demographics_user_ethnicity: ["asian", "white", "other"]
|
||||||
|
//
|
||||||
|
// Demographics wants (example):
|
||||||
|
// user_ethnicity: [
|
||||||
|
// { ethnicity: "asian" },
|
||||||
|
// { ethnicity: "white" },
|
||||||
|
// { ethnicity: "other" }
|
||||||
|
// ]
|
||||||
|
function convertEthnicity(ethnicityData, direction) {
|
||||||
|
if (direction === FROM) {
|
||||||
|
return ethnicityData.map(e => e.ethnicity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === TO) {
|
||||||
|
return ethnicityData.map(e => ({ ethnicity: e }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethnicityData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles conversion of data to/from Demographics IDA to/from format needed for
|
||||||
|
// frontend
|
||||||
|
// * handles ethnicity field
|
||||||
|
// * adds/removes 'demographics' to/from key
|
||||||
|
// * replace `null` with empty string or empty string with null
|
||||||
|
export function convertData(dataObject, direction) {
|
||||||
|
const converted = {};
|
||||||
|
|
||||||
|
Object.entries(dataObject).forEach(([key, value]) => {
|
||||||
|
let newValue = value;
|
||||||
|
|
||||||
|
if (key.includes('ethnicity')) {
|
||||||
|
newValue = convertEthnicity(value, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === TO) {
|
||||||
|
converted[key.replace('demographics_', '')] = newValue || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction === FROM) {
|
||||||
|
converted[`demographics_${key}`] = newValue || '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return converted;
|
||||||
|
}
|
||||||
@@ -0,0 +1,584 @@
|
|||||||
|
/* eslint-disable no-import-assign */
|
||||||
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import React from 'react';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import DemographicsSection from '../DemographicsSection';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
|
||||||
|
const IntlDemographicsSection = injectIntl(DemographicsSection);
|
||||||
|
|
||||||
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ demographicsSectionSelector: () => ({}) })));
|
||||||
|
|
||||||
|
const mockStore = configureStore();
|
||||||
|
|
||||||
|
describe('DemographicsSection', () => {
|
||||||
|
let props = {};
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore();
|
||||||
|
props = {
|
||||||
|
updateDraft: jest.fn(),
|
||||||
|
formValues: {
|
||||||
|
demographics_gender: 'declined',
|
||||||
|
demographics_gender_description: '',
|
||||||
|
demographics_user_ethnicity: [],
|
||||||
|
demographics_income: 'declined',
|
||||||
|
demographics_military_history: 'declined',
|
||||||
|
demographics_learner_education_level: 'declined',
|
||||||
|
demographics_parent_education_level: 'declined',
|
||||||
|
demographics_work_status: 'declined',
|
||||||
|
demographics_work_status_description: '',
|
||||||
|
demographics_current_work_sector: 'declined',
|
||||||
|
demographics_future_work_sector: 'declined',
|
||||||
|
demographics_user: 1,
|
||||||
|
demographicsOptions: {
|
||||||
|
actions: {
|
||||||
|
POST: {
|
||||||
|
gender: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'woman',
|
||||||
|
display_name: 'Woman',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'man',
|
||||||
|
display_name: 'Man',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'non-binary',
|
||||||
|
display_name: 'Non-binary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'self-describe',
|
||||||
|
display_name: 'Prefer to self describe',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
income: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'less-than-10k',
|
||||||
|
display_name: 'Less than US $10,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '10k-25k',
|
||||||
|
display_name: 'US $10,000 - $25,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '25k-50k',
|
||||||
|
display_name: 'US $25,000 - $50,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '50k-75k',
|
||||||
|
display_name: 'US $50,000 - $75,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '75k-100k',
|
||||||
|
display_name: 'US $75,000 - $100,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'over-100k',
|
||||||
|
display_name: 'Over US $100,000',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'unsure',
|
||||||
|
display_name: "I don't know",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
learner_education_level: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'no-high-school',
|
||||||
|
display_name: 'No High School',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'some-high-school',
|
||||||
|
display_name: 'Some High School',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'high-school-ged-equivalent',
|
||||||
|
display_name: 'High School diploma, GED, or equivalent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'some-college',
|
||||||
|
display_name: 'Some college, but no degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'associates',
|
||||||
|
display_name: 'Associates degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'bachelors',
|
||||||
|
display_name: 'Bachelors degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'masters',
|
||||||
|
display_name: 'Masters degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'professional',
|
||||||
|
display_name: 'Professional degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'doctorate',
|
||||||
|
display_name: 'Doctorate degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
parent_education_level: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'no-high-school',
|
||||||
|
display_name: 'No High School',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'some-high-school',
|
||||||
|
display_name: 'Some High School',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'high-school-ged-equivalent',
|
||||||
|
display_name: 'High School diploma, GED, or equivalent',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'some-college',
|
||||||
|
display_name: 'Some college, but no degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'associates',
|
||||||
|
display_name: 'Associates degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'bachelors',
|
||||||
|
display_name: 'Bachelors degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'masters',
|
||||||
|
display_name: 'Masters degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'professional',
|
||||||
|
display_name: 'Professional degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'doctorate',
|
||||||
|
display_name: 'Doctorate degree',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
military_history: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'never-served',
|
||||||
|
display_name: 'Never served in the military',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'training',
|
||||||
|
display_name: 'Only on active duty for training',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'active',
|
||||||
|
display_name: 'Now on active duty',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'previously-active',
|
||||||
|
display_name: 'On active duty in the past, but not now',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
work_status: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'full-time',
|
||||||
|
display_name: 'Employed, working full-time',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'part-time',
|
||||||
|
display_name: 'Employed, working part-time',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'self-employed',
|
||||||
|
display_name: 'Self-Employed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not-employed-looking',
|
||||||
|
display_name: 'Not employed, looking for work',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not-employed-not-looking',
|
||||||
|
display_name: 'Not employed, not looking for work',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'unable',
|
||||||
|
display_name: 'Unable to work',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'retired',
|
||||||
|
display_name: 'Retired',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
display_name: 'Other',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
current_work_sector: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'accommodation-food',
|
||||||
|
display_name: 'Accommodation and Food Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'administrative-support-waste-remediation',
|
||||||
|
display_name: 'Administrative and Support and Waste Management and Remediation Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'agriculture-forestry-fishing-hunting',
|
||||||
|
display_name: 'Agriculture, Forestry, Fishing and Hunting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'arts-entertainment-recreation',
|
||||||
|
display_name: 'Arts, Entertainment, and Recreation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'construction',
|
||||||
|
display_name: 'Construction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'educational',
|
||||||
|
display_name: 'Education Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'finance-insurance',
|
||||||
|
display_name: 'Finance and Insurance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'healthcare-social',
|
||||||
|
display_name: 'Health Care and Social Assistance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'information',
|
||||||
|
display_name: 'Information',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'management',
|
||||||
|
display_name: 'Management of Companies and Enterprises',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'manufacturing',
|
||||||
|
display_name: 'Manufacturing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'mining-quarry-oil-gas',
|
||||||
|
display_name: 'Mining, Quarrying, and Oil and Gas Extraction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'professional-scientific-technical',
|
||||||
|
display_name: 'Professional, Scientific, and Technical Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'public-admin',
|
||||||
|
display_name: 'Public Administration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'real-estate',
|
||||||
|
display_name: 'Real Estate and Rental and Leasing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'retail',
|
||||||
|
display_name: 'Retail Trade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'transport-warehousing',
|
||||||
|
display_name: 'Transportation and Warehousing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'utilities',
|
||||||
|
display_name: 'Utilities',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'trade',
|
||||||
|
display_name: 'Wholesale Trade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
display_name: 'Other',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
future_work_sector: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'accommodation-food',
|
||||||
|
display_name: 'Accommodation and Food Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'administrative-support-waste-remediation',
|
||||||
|
display_name: 'Administrative and Support and Waste Management and Remediation Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'agriculture-forestry-fishing-hunting',
|
||||||
|
display_name: 'Agriculture, Forestry, Fishing and Hunting',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'arts-entertainment-recreation',
|
||||||
|
display_name: 'Arts, Entertainment, and Recreation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'construction',
|
||||||
|
display_name: 'Construction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'educational',
|
||||||
|
display_name: 'Education Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'finance-insurance',
|
||||||
|
display_name: 'Finance and Insurance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'healthcare-social',
|
||||||
|
display_name: 'Health Care and Social Assistance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'information',
|
||||||
|
display_name: 'Information',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'management',
|
||||||
|
display_name: 'Management of Companies and Enterprises',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'manufacturing',
|
||||||
|
display_name: 'Manufacturing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'mining-quarry-oil-gas',
|
||||||
|
display_name: 'Mining, Quarrying, and Oil and Gas Extraction',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'professional-scientific-technical',
|
||||||
|
display_name: 'Professional, Scientific, and Technical Services',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'public-admin',
|
||||||
|
display_name: 'Public Administration',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'real-estate',
|
||||||
|
display_name: 'Real Estate and Rental and Leasing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'retail',
|
||||||
|
display_name: 'Retail Trade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'transport-warehousing',
|
||||||
|
display_name: 'Transportation and Warehousing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'utilities',
|
||||||
|
display_name: 'Utilities',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'trade',
|
||||||
|
display_name: 'Wholesale Trade',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
display_name: 'Other',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
user_ethnicity: {
|
||||||
|
child: {
|
||||||
|
children: {
|
||||||
|
ethnicity: {
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
value: 'american-indian-or-alaska-native',
|
||||||
|
display_name: 'American Indian or Alaska Native',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'asian',
|
||||||
|
display_name: 'Asian',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'black-or-african-american',
|
||||||
|
display_name: 'Black or African American',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'hispanic-latin-spanish',
|
||||||
|
display_name: 'Hispanic, Latin, or Spanish origin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'middle-eastern-or-north-african',
|
||||||
|
display_name: 'Middle Eastern or North African',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'native-hawaiian-or-pacific-islander',
|
||||||
|
display_name: 'Native Hawaiian or Other Pacific Islander',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'white',
|
||||||
|
display_name: 'White',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'other',
|
||||||
|
display_name: 'Some other race, ethnicity, or origin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'declined',
|
||||||
|
display_name: 'Prefer not to respond',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formErrors: {},
|
||||||
|
intl: {},
|
||||||
|
forwardRef: () => {},
|
||||||
|
drafts: {},
|
||||||
|
};
|
||||||
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
|
patch: async () => ({
|
||||||
|
data: { status: 200 },
|
||||||
|
catch: () => {},
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 1 }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render', () => {
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render an Alert if an error occurs', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formErrors: {
|
||||||
|
demographicsError: 'api-error',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set user input correctly when user provides gender self-description', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formValues: {
|
||||||
|
...props.formValues,
|
||||||
|
demographics_gender: 'self-describe',
|
||||||
|
demographics_gender_description: 'test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set user input correctly when user provides answers to work_status question', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formValues: {
|
||||||
|
...props.formValues,
|
||||||
|
demographics_work_status: 'other',
|
||||||
|
demographics_work_status_description: 'test',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render ethnicity text correctly', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formValues: {
|
||||||
|
...props.formValues,
|
||||||
|
demographics_user_ethnicity: ['asian'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render ethnicity correctly when multiple options are selected', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formValues: {
|
||||||
|
...props.formValues,
|
||||||
|
demographics_user_ethnicity: ['hispanic-latin-spanish', 'white'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render an Alert when demographicsOptions props are empty', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
formValues: {
|
||||||
|
demographicsOptions: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,3946 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`DemographicsSection should render 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="p-0 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add race/ethnicity identity
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
tabIndex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert d-flex align-items-start alert alert-danger"
|
||||||
|
>
|
||||||
|
<div />
|
||||||
|
<div>
|
||||||
|
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="p-0 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add race/ethnicity identity
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should render an Alert when demographicsOptions props are empty 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
tabIndex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="alert d-flex align-items-start alert alert-danger"
|
||||||
|
>
|
||||||
|
<div />
|
||||||
|
<div>
|
||||||
|
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should render ethnicity correctly when multiple options are selected 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Hispanic, Latin, or Spanish origin, White
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Asian
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should set user input correctly when user provides answers to work_status question 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="p-0 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add race/ethnicity identity
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Other: test
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`DemographicsSection should set user input correctly when user provides gender self-description 1`] = `
|
||||||
|
<div
|
||||||
|
className="account-section pt-3 mb-5"
|
||||||
|
id="demographics-information"
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
className="section-heading h4 mb-3"
|
||||||
|
>
|
||||||
|
Optional Information
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://localhost:5335/demographics"
|
||||||
|
onClick={[Function]}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Why does localhost collect this information?
|
||||||
|
<span
|
||||||
|
className="pgn__hyperlink__external-icon"
|
||||||
|
title="Opens in a new tab"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "1em",
|
||||||
|
"width": "1em",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span
|
||||||
|
className="sr-only"
|
||||||
|
>
|
||||||
|
in a new tab
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="demographics-fields"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Gender identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer to self describe: test
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Race/Ethnicity identity
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="p-0 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Add race/ethnicity identity
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Family income
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
U.S. Military status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Your education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Parents/Guardians education level
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Employment status
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Current work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn-transition-replace-group position-relative"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"padding": ".1px 0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="d-flex align-items-start"
|
||||||
|
>
|
||||||
|
<h6
|
||||||
|
aria-level="3"
|
||||||
|
>
|
||||||
|
Future work industry
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
className="ml-3 btn btn-link"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||||
|
data-icon="pencil-alt"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={Object {}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={Object {}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
className={null}
|
||||||
|
data-hj-suppress={true}
|
||||||
|
>
|
||||||
|
Prefer not to respond
|
||||||
|
</p>
|
||||||
|
<p
|
||||||
|
className="small text-muted mt-n2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
export const withNavigate = Component => {
|
|
||||||
const WrappedComponent = props => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
return <Component {...props} navigate={navigate} />;
|
|
||||||
};
|
|
||||||
return WrappedComponent;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const withLocation = Component => {
|
|
||||||
const WrappedComponent = props => {
|
|
||||||
const location = useLocation();
|
|
||||||
return <Component {...props} location={location.pathname} />;
|
|
||||||
};
|
|
||||||
return WrappedComponent;
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
|
||||||
import { withLocation, withNavigate } from './hoc';
|
|
||||||
|
|
||||||
const mockedNavigator = jest.fn();
|
|
||||||
|
|
||||||
jest.mock('react-router-dom', () => ({
|
|
||||||
useNavigate: () => mockedNavigator,
|
|
||||||
useLocation: () => ({
|
|
||||||
pathname: '/current-location',
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
|
||||||
const MockComponent = ({ navigate, location }) => (
|
|
||||||
// eslint-disable-next-line react/button-has-type, react/prop-types
|
|
||||||
<button data-testid="btn" onClick={() => navigate('/some-route')}>{location}</button>
|
|
||||||
);
|
|
||||||
const WrappedComponent = withNavigate(withLocation(MockComponent));
|
|
||||||
|
|
||||||
test('Provide Navigation to Component', () => {
|
|
||||||
render(
|
|
||||||
<WrappedComponent />,
|
|
||||||
);
|
|
||||||
const btn = screen.getByTestId('btn');
|
|
||||||
fireEvent.click(btn);
|
|
||||||
|
|
||||||
expect(mockedNavigator).toHaveBeenCalledWith('/some-route');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Provide Location Pathname to Component', () => {
|
|
||||||
render(
|
|
||||||
<WrappedComponent />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('btn').textContent).toContain('/current-location');
|
|
||||||
});
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
ActionRow,
|
ActionRow,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
StatefulButton,
|
StatefulButton,
|
||||||
} from '@openedx/paragon';
|
} from '@edx/paragon';
|
||||||
|
|
||||||
import { closeForm, saveSettingsReset } from '../data/actions';
|
import { closeForm, saveSettingsReset } from '../data/actions';
|
||||||
import { nameChangeSelector } from '../data/selectors';
|
import { nameChangeSelector } from '../data/selectors';
|
||||||
@@ -25,14 +25,14 @@ const NameChangeModal = ({
|
|||||||
targetFormId,
|
targetFormId,
|
||||||
errors,
|
errors,
|
||||||
formValues,
|
formValues,
|
||||||
|
intl,
|
||||||
saveState,
|
saveState,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const { push } = useHistory();
|
||||||
const { username } = getAuthenticatedUser();
|
const { username } = getAuthenticatedUser();
|
||||||
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
||||||
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const resetLocalState = useCallback(() => {
|
const resetLocalState = useCallback(() => {
|
||||||
setConfirmedWarning(false);
|
setConfirmedWarning(false);
|
||||||
@@ -69,9 +69,9 @@ const NameChangeModal = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (saveState === 'complete') {
|
if (saveState === 'complete') {
|
||||||
handleClose();
|
handleClose();
|
||||||
navigate(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
push(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
||||||
}
|
}
|
||||||
}, [handleClose, navigate, saveState]);
|
}, [handleClose, push, saveState]);
|
||||||
|
|
||||||
function renderErrors() {
|
function renderErrors() {
|
||||||
if (Object.keys(errors).length > 0) {
|
if (Object.keys(errors).length > 0) {
|
||||||
@@ -193,10 +193,11 @@ NameChangeModal.propTypes = {
|
|||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
saveState: PropTypes.string,
|
saveState: PropTypes.string,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
NameChangeModal.defaultProps = {
|
NameChangeModal.defaultProps = {
|
||||||
saveState: null,
|
saveState: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(nameChangeSelector)(NameChangeModal);
|
export default connect(nameChangeSelector)(injectIntl(NameChangeModal));
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { handleRequestError } from '../../data/utils';
|
|
||||||
|
|
||||||
import { postNameChange } from './service';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('../../data/utils');
|
|
||||||
|
|
||||||
describe('postNameChange', () => {
|
|
||||||
const mockPost = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({
|
|
||||||
LMS_BASE_URL: 'http://testserver',
|
|
||||||
});
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({
|
|
||||||
post: mockPost,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('posts a name change request successfully', async () => {
|
|
||||||
const mockResponse = { data: { success: true, updated: true } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const result = await postNameChange('New Name');
|
|
||||||
|
|
||||||
expect(getConfig).toHaveBeenCalled();
|
|
||||||
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/api/user/v1/accounts/name_change/',
|
|
||||||
{ name: 'New Name' },
|
|
||||||
{ headers: { Accept: 'application/json' } },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(mockResponse.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls handleRequestError and throws when request fails', async () => {
|
|
||||||
const mockError = new Error('Request failed');
|
|
||||||
mockPost.mockRejectedValueOnce(mockError);
|
|
||||||
|
|
||||||
handleRequestError.mockImplementation(() => {
|
|
||||||
throw mockError;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(postNameChange('Bad Name')).rejects.toThrow('Request failed');
|
|
||||||
|
|
||||||
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -23,7 +23,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
'account.settings.name.change.id.name.label': {
|
'account.settings.name.change.id.name.label': {
|
||||||
id: 'account.settings.name.change.id.name.label',
|
id: 'account.settings.name.change.id.name.label',
|
||||||
defaultMessage: 'Enter your name as it appears on your identification card.',
|
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
|
||||||
description: 'Form label instructing the user to enter the name on their ID.',
|
description: 'Form label instructing the user to enter the name on their ID.',
|
||||||
},
|
},
|
||||||
'account.settings.name.change.id.name.placeholder': {
|
'account.settings.name.change.id.name.placeholder': {
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
import { Router } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {
|
import {
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
|
|
||||||
import * as auth from '@edx/frontend-platform/auth';
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||||
jest.mock('react-dom', () => ({
|
ReactDOM.createPortal = node => node;
|
||||||
...jest.requireActual('react-dom'),
|
|
||||||
createPortal: jest.fn(node => node), // Mock portal behavior
|
|
||||||
}));
|
|
||||||
|
|
||||||
import NameChange from '../NameChange'; // eslint-disable-line import/first
|
import NameChange from '../NameChange'; // eslint-disable-line import/first
|
||||||
|
|
||||||
@@ -28,6 +28,10 @@ jest.mock('react-redux', () => ({
|
|||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ nameChangeSelector: () => ({}) })));
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ nameChangeSelector: () => ({}) })));
|
||||||
|
|
||||||
|
const history = createMemoryHistory();
|
||||||
|
|
||||||
|
const IntlNameChange = injectIntl(NameChange);
|
||||||
|
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
|
|
||||||
describe('NameChange', () => {
|
describe('NameChange', () => {
|
||||||
@@ -35,7 +39,7 @@ describe('NameChange', () => {
|
|||||||
let store = {};
|
let store = {};
|
||||||
|
|
||||||
const reduxWrapper = children => (
|
const reduxWrapper = children => (
|
||||||
<Router>
|
<Router history={history}>
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<Provider store={store}>{children}</Provider>
|
<Provider store={store}>{children}</Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
@@ -52,6 +56,7 @@ describe('NameChange', () => {
|
|||||||
verified_name: 'edX Verified',
|
verified_name: 'edX Verified',
|
||||||
},
|
},
|
||||||
saveState: null,
|
saveState: null,
|
||||||
|
intl: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
@@ -68,7 +73,7 @@ describe('NameChange', () => {
|
|||||||
it('renders populated input after clicking continue if verified_name in form data', async () => {
|
it('renders populated input after clicking continue if verified_name in form data', async () => {
|
||||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
||||||
|
|
||||||
render(reduxWrapper(<NameChange {...props} />));
|
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||||
expect(getInput()).toBeNull();
|
expect(getInput()).toBeNull();
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
@@ -85,7 +90,7 @@ describe('NameChange', () => {
|
|||||||
name: 'edx edx',
|
name: 'edx edx',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
render(reduxWrapper(<NameChange {...formProps} />));
|
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -103,7 +108,7 @@ describe('NameChange', () => {
|
|||||||
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<NameChange {...props} />));
|
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -130,7 +135,7 @@ describe('NameChange', () => {
|
|||||||
targetFormId: 'name',
|
targetFormId: 'name',
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<NameChange {...formProps} />));
|
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -146,7 +151,7 @@ describe('NameChange', () => {
|
|||||||
it('does not dispatch action while pending', async () => {
|
it('does not dispatch action while pending', async () => {
|
||||||
props.saveState = 'pending';
|
props.saveState = 'pending';
|
||||||
|
|
||||||
render(reduxWrapper(<NameChange {...props} />));
|
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -162,7 +167,7 @@ describe('NameChange', () => {
|
|||||||
it('routes to IDV when name change request is successful', async () => {
|
it('routes to IDV when name change request is successful', async () => {
|
||||||
props.saveState = 'complete';
|
props.saveState = 'complete';
|
||||||
|
|
||||||
render(reduxWrapper(<NameChange {...props} />));
|
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||||
expect(window.location.pathname).toEqual('/id-verification');
|
expect(history.location.pathname).toEqual('/id-verification');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink } from '@openedx/paragon';
|
import { Hyperlink } from '@edx/paragon';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ const ConfirmationAlert = (props) => {
|
|||||||
|
|
||||||
const technicalSupportLink = (
|
const technicalSupportLink = (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
destination={getConfig().PASSWORD_RESET_SUPPORT_LINK}
|
destination="https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-"
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
|
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import { StatefulButton } from '@openedx/paragon';
|
import { StatefulButton } from '@edx/paragon';
|
||||||
|
|
||||||
import { resetPassword } from './data/actions';
|
import { resetPassword } from './data/actions';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -9,9 +10,7 @@ import ConfirmationAlert from './ConfirmationAlert';
|
|||||||
import RequestInProgressAlert from './RequestInProgressAlert';
|
import RequestInProgressAlert from './RequestInProgressAlert';
|
||||||
|
|
||||||
const ResetPassword = (props) => {
|
const ResetPassword = (props) => {
|
||||||
const { email, status } = props;
|
const { email, intl, status } = props;
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<h6 aria-level="3">
|
<h6 aria-level="3">
|
||||||
@@ -52,6 +51,7 @@ const ResetPassword = (props) => {
|
|||||||
|
|
||||||
ResetPassword.propTypes = {
|
ResetPassword.propTypes = {
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
|
intl: intlShape.isRequired,
|
||||||
resetPassword: PropTypes.func.isRequired,
|
resetPassword: PropTypes.func.isRequired,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
};
|
};
|
||||||
@@ -68,4 +68,4 @@ export default connect(
|
|||||||
{
|
{
|
||||||
resetPassword,
|
resetPassword,
|
||||||
},
|
},
|
||||||
)(ResetPassword);
|
)(injectIntl(ResetPassword));
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import formurlencoded from 'form-urlencoded';
|
|
||||||
import { handleRequestError } from '../../data/utils';
|
|
||||||
|
|
||||||
import { postResetPassword } from './service';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('form-urlencoded');
|
|
||||||
jest.mock('../../data/utils');
|
|
||||||
|
|
||||||
describe('postResetPassword', () => {
|
|
||||||
const mockPost = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({
|
|
||||||
LMS_BASE_URL: 'http://testserver',
|
|
||||||
});
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({
|
|
||||||
post: mockPost,
|
|
||||||
});
|
|
||||||
|
|
||||||
formurlencoded.mockImplementation(obj => `encoded:${JSON.stringify(obj)}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('posts reset password request with email', async () => {
|
|
||||||
const mockResponse = { data: { success: true, email_sent: true } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const result = await postResetPassword('user@example.com');
|
|
||||||
|
|
||||||
expect(getConfig).toHaveBeenCalled();
|
|
||||||
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
|
||||||
expect(formurlencoded).toHaveBeenCalledWith({ email: 'user@example.com' });
|
|
||||||
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/password_reset/',
|
|
||||||
'encoded:{"email":"user@example.com"}',
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(mockResponse.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls handleRequestError and throws when request fails', async () => {
|
|
||||||
const mockError = new Error('Reset password failed');
|
|
||||||
mockPost.mockRejectedValueOnce(mockError);
|
|
||||||
|
|
||||||
handleRequestError.mockImplementation(() => {
|
|
||||||
throw mockError;
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(postResetPassword('bad@example.com')).rejects.toThrow('Reset password failed');
|
|
||||||
|
|
||||||
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -2,9 +2,8 @@ import { AsyncActionType } from '../data/utils';
|
|||||||
|
|
||||||
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
||||||
|
|
||||||
export const fetchSiteLanguages = handleNavigation => ({
|
export const fetchSiteLanguages = () => ({
|
||||||
type: FETCH_SITE_LANGUAGES.BASE,
|
type: FETCH_SITE_LANGUAGES.BASE,
|
||||||
payload: { handleNavigation },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchSiteLanguagesBegin = () => ({
|
export const fetchSiteLanguagesBegin = () => ({
|
||||||
|
|||||||
@@ -19,11 +19,6 @@ const siteLanguageList = [
|
|||||||
name: 'Español (Latinoamérica)',
|
name: 'Español (Latinoamérica)',
|
||||||
released: true,
|
released: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
code: 'fa-ir',
|
|
||||||
name: 'فارسی',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
code: 'fr',
|
code: 'fr',
|
||||||
name: 'Français',
|
name: 'Français',
|
||||||
@@ -74,61 +69,6 @@ const siteLanguageList = [
|
|||||||
name: '中文 (简体)',
|
name: '中文 (简体)',
|
||||||
released: true,
|
released: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
code: 'pt-pt',
|
|
||||||
name: 'Português',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'it-it',
|
|
||||||
name: 'Italian',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'de-de',
|
|
||||||
name: 'German',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'hi',
|
|
||||||
name: 'Hindi',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'fr-ca',
|
|
||||||
name: 'French (CA)',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'te',
|
|
||||||
name: 'తెలుగు',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'da',
|
|
||||||
name: 'dansk',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'el',
|
|
||||||
name: 'Ελληνικά',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'es-es',
|
|
||||||
name: 'Español (España)',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'sw',
|
|
||||||
name: 'Kiswahili',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'tr-tr',
|
|
||||||
name: 'Türkçe (Türkiye)',
|
|
||||||
released: true,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export default siteLanguageList;
|
export default siteLanguageList;
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import {
|
|||||||
import { getSiteLanguageList } from './service';
|
import { getSiteLanguageList } from './service';
|
||||||
import { handleFailure } from '../data/utils';
|
import { handleFailure } from '../data/utils';
|
||||||
|
|
||||||
function* handleFetchSiteLanguages(action) {
|
function* handleFetchSiteLanguages() {
|
||||||
try {
|
try {
|
||||||
yield put(fetchSiteLanguagesBegin());
|
yield put(fetchSiteLanguagesBegin());
|
||||||
const siteLanguageList = yield call(getSiteLanguageList);
|
const siteLanguageList = yield call(getSiteLanguageList);
|
||||||
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield call(handleFailure, e, action.payload.handleNavigation, fetchSiteLanguagesFailure);
|
yield call(handleFailure, e, fetchSiteLanguagesFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,15 +23,10 @@ export async function patchPreferences(username, params) {
|
|||||||
|
|
||||||
export async function postSetLang(code) {
|
export async function postSetLang(code) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const requestConfig = {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`;
|
|
||||||
formData.append('language', code);
|
formData.append('language', code);
|
||||||
|
|
||||||
await getAuthenticatedHttpClient()
|
await getAuthenticatedHttpClient()
|
||||||
.post(url, formData, requestConfig);
|
.post(`${getConfig().LMS_BASE_URL}/i18n/setlang/`, formData, {
|
||||||
|
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils';
|
|
||||||
|
|
||||||
import { getSiteLanguageList, patchPreferences, postSetLang } from './service';
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform');
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('@edx/frontend-platform/utils');
|
|
||||||
jest.mock('./constants', () => (['en', 'es', 'fr']));
|
|
||||||
|
|
||||||
describe('preferencesApi', () => {
|
|
||||||
const mockPatch = jest.fn();
|
|
||||||
const mockPost = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks();
|
|
||||||
|
|
||||||
getConfig.mockReturnValue({
|
|
||||||
LMS_BASE_URL: 'http://testserver',
|
|
||||||
});
|
|
||||||
|
|
||||||
getAuthenticatedHttpClient.mockReturnValue({
|
|
||||||
patch: mockPatch,
|
|
||||||
post: mockPost,
|
|
||||||
});
|
|
||||||
|
|
||||||
snakeCaseObject.mockImplementation(obj => obj);
|
|
||||||
convertKeyNames.mockImplementation((obj) => obj);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getSiteLanguageList', () => {
|
|
||||||
it('returns the siteLanguageList constant', async () => {
|
|
||||||
const result = await getSiteLanguageList();
|
|
||||||
expect(result).toEqual(['en', 'es', 'fr']);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('patchPreferences', () => {
|
|
||||||
it('patches preferences with processed params and returns the original params', async () => {
|
|
||||||
const username = 'testuser';
|
|
||||||
const params = { prefLang: 'en', darkMode: true };
|
|
||||||
const processed = { 'pref-lang': 'en', dark_mode: true };
|
|
||||||
|
|
||||||
// Mock conversions
|
|
||||||
snakeCaseObject.mockReturnValueOnce({ pref_lang: 'en', dark_mode: true });
|
|
||||||
convertKeyNames.mockReturnValueOnce(processed);
|
|
||||||
|
|
||||||
mockPatch.mockResolvedValueOnce({ data: { success: true } });
|
|
||||||
|
|
||||||
const result = await patchPreferences(username, params);
|
|
||||||
|
|
||||||
expect(snakeCaseObject).toHaveBeenCalledWith(params);
|
|
||||||
expect(convertKeyNames).toHaveBeenCalledWith(
|
|
||||||
{ pref_lang: 'en', dark_mode: true },
|
|
||||||
{ pref_lang: 'pref-lang' },
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(mockPatch).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/api/user/v1/preferences/testuser',
|
|
||||||
processed,
|
|
||||||
{
|
|
||||||
headers: { 'Content-Type': 'application/merge-patch+json' },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(params);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('postSetLang', () => {
|
|
||||||
it('posts language selection via FormData', async () => {
|
|
||||||
const mockResponse = { data: { success: true } };
|
|
||||||
mockPost.mockResolvedValueOnce(mockResponse);
|
|
||||||
|
|
||||||
const appendSpy = jest.spyOn(FormData.prototype, 'append');
|
|
||||||
|
|
||||||
await postSetLang('fr');
|
|
||||||
|
|
||||||
expect(appendSpy).toHaveBeenCalledWith('language', 'fr');
|
|
||||||
expect(mockPost).toHaveBeenCalledWith(
|
|
||||||
'http://testserver/i18n/setlang/',
|
|
||||||
expect.any(FormData),
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
appendSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import thunk from 'redux-thunk';
|
|
||||||
import { AppContext } from '@edx/frontend-platform/react';
|
|
||||||
import {
|
|
||||||
render, screen, fireEvent,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import AccountSettingsPage from '../AccountSettingsPage';
|
|
||||||
import mockData from './mockData';
|
|
||||||
import messages from '../AccountSettingsPage.messages';
|
|
||||||
|
|
||||||
const mockDispatch = jest.fn();
|
|
||||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
|
||||||
sendTrackingLogEvent: jest.fn(),
|
|
||||||
getCountryList: jest.fn(() => [{ code: 'US', name: 'United States' }]),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('react-redux', () => ({
|
|
||||||
...jest.requireActual('react-redux'),
|
|
||||||
useDispatch: () => mockDispatch,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform', () => ({
|
|
||||||
...jest.requireActual('@edx/frontend-platform'),
|
|
||||||
getConfig: jest.fn(() => ({
|
|
||||||
SITE_NAME: 'edX',
|
|
||||||
SUPPORT_URL: 'https://support.edx.org',
|
|
||||||
ENABLE_ACCOUNT_DELETION: true,
|
|
||||||
ENABLE_COPPA_COMPLIANCE: false,
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
|
||||||
})),
|
|
||||||
getCountryList: jest.fn(() => [{ code: 'US', name: 'United States' }]),
|
|
||||||
getLanguageList: jest.fn(() => [{ code: 'en', name: 'English' }]),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const IntlAccountSettingsPage = injectIntl(AccountSettingsPage);
|
|
||||||
|
|
||||||
const middlewares = [thunk];
|
|
||||||
const mockStore = configureStore(middlewares);
|
|
||||||
|
|
||||||
describe('AccountSettingsPage', () => {
|
|
||||||
let props = {};
|
|
||||||
let store = {};
|
|
||||||
const appContext = { locale: 'en', authenticatedUser: { userId: 3, roles: [] } };
|
|
||||||
const reduxWrapper = children => (
|
|
||||||
<AppContext.Provider value={appContext}>
|
|
||||||
<Router>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<Provider store={store}>
|
|
||||||
{children}
|
|
||||||
</Provider>
|
|
||||||
</IntlProvider>
|
|
||||||
</Router>
|
|
||||||
</AppContext.Provider>
|
|
||||||
);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store = mockStore(mockData);
|
|
||||||
props = {
|
|
||||||
loaded: true,
|
|
||||||
siteLanguage: {},
|
|
||||||
formValues: {
|
|
||||||
username: 'test_username',
|
|
||||||
accomplishments_shared: false,
|
|
||||||
name: 'test_name',
|
|
||||||
email: 'test_email@test.com',
|
|
||||||
id: 534,
|
|
||||||
extended_profile: [
|
|
||||||
{
|
|
||||||
field_name: 'work_experience',
|
|
||||||
field_value: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
country: 'US',
|
|
||||||
level_of_education: 'b',
|
|
||||||
gender: 'm',
|
|
||||||
language_proficiencies: 'es',
|
|
||||||
social_link_linkedin: 'https://linkedin.com/in/testuser',
|
|
||||||
social_link_facebook: '',
|
|
||||||
social_link_twitter: '',
|
|
||||||
time_zone: 'America/New_York',
|
|
||||||
state: 'NY',
|
|
||||||
secondary_email_enabled: true,
|
|
||||||
secondary_email: 'test_recovery@test.com',
|
|
||||||
year_of_birth: '1990',
|
|
||||||
},
|
|
||||||
fetchSettings: jest.fn(),
|
|
||||||
fetchSiteLanguages: jest.fn(),
|
|
||||||
fetchNotificationPreferences: jest.fn(),
|
|
||||||
saveSettings: jest.fn(),
|
|
||||||
updateDraft: jest.fn(),
|
|
||||||
beginNameChange: jest.fn(),
|
|
||||||
saveMultipleSettings: jest.fn(),
|
|
||||||
timeZoneOptions: [
|
|
||||||
{ label: 'America/New_York', value: 'America/New_York' },
|
|
||||||
],
|
|
||||||
countryTimeZoneOptions: [
|
|
||||||
{ label: 'America/New_York', value: 'America/New_York' },
|
|
||||||
],
|
|
||||||
siteLanguageOptions: [
|
|
||||||
{ label: 'English', value: 'en' },
|
|
||||||
],
|
|
||||||
tpaProviders: [
|
|
||||||
{
|
|
||||||
id: 'oa2-google-oauth2',
|
|
||||||
name: 'Google',
|
|
||||||
connected: false,
|
|
||||||
accepts_logins: true,
|
|
||||||
connectUrl: 'http://localhost:18000/auth/login/google-oauth2/',
|
|
||||||
disconnectUrl: 'http://localhost:18000/auth/disconnect/google-oauth2/',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isActive: true,
|
|
||||||
staticFields: [],
|
|
||||||
profileDataManager: null,
|
|
||||||
verifiedName: null,
|
|
||||||
mostRecentVerifiedName: {},
|
|
||||||
verifiedNameHistory: [],
|
|
||||||
countriesCodesList: ['US'],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => jest.clearAllMocks());
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
global.lightningjs = {
|
|
||||||
require: jest.fn().mockImplementation((module, url) => ({ moduleName: module, url })),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
delete global.lightningjs;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders AccountSettingsPage correctly with editing enabled', async () => {
|
|
||||||
const { getByText, rerender, getByLabelText } = render(reduxWrapper(<IntlAccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
const workExperienceText = getByText('Work Experience');
|
|
||||||
const workExperienceEditButton = workExperienceText.parentElement.querySelector('button');
|
|
||||||
|
|
||||||
expect(workExperienceEditButton).toBeInTheDocument();
|
|
||||||
|
|
||||||
store = mockStore({
|
|
||||||
...mockData,
|
|
||||||
accountSettings: {
|
|
||||||
...mockData.accountSettings,
|
|
||||||
openFormId: 'work_experience',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
rerender(reduxWrapper(<IntlAccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
const submitButton = screen.getByText('Save');
|
|
||||||
expect(submitButton).toBeInTheDocument();
|
|
||||||
|
|
||||||
const workExperienceSelect = getByLabelText('Work Experience');
|
|
||||||
|
|
||||||
// Use fireEvent.change to simulate changing the selected value
|
|
||||||
fireEvent.change(workExperienceSelect, { target: { value: '4' } });
|
|
||||||
|
|
||||||
fireEvent.click(submitButton);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders Account Information section with correct field values', () => {
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.getByText('test_username')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('test_name')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('test_email@test.com')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('test_recovery@test.com')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('1990')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders Profile Information section with correct field values', () => {
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.getByText('Bachelor\'s Degree')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Male')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Add work experience')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('English')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders Social Media section with correct field values', () => {
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.getByText('https://linkedin.com/in/testuser')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Add Facebook profile')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(messages['account.settings.field.social.platform.name.xTwitter.empty'].defaultMessage)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders Site Preferences section with correct field values', () => {
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.getByText('English')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('America/New_York')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders Delete Account section when enabled', () => {
|
|
||||||
// eslint-disable-next-line global-require
|
|
||||||
const { getConfig } = require('@edx/frontend-platform');
|
|
||||||
jest.spyOn({ getConfig }, 'getConfig').mockImplementation(() => ({
|
|
||||||
SITE_NAME: 'edX',
|
|
||||||
SUPPORT_URL: 'https://support.edx.org',
|
|
||||||
ENABLE_ACCOUNT_DELETION: true,
|
|
||||||
ENABLE_COPPA_COMPLIANCE: false,
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.getByText('We\'re sorry to see you go!')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render Delete Account section when disabled', () => {
|
|
||||||
// eslint-disable-next-line global-require
|
|
||||||
const { getConfig } = require('@edx/frontend-platform');
|
|
||||||
jest.spyOn({ getConfig }, 'getConfig').mockImplementation(() => ({
|
|
||||||
SITE_NAME: 'edX',
|
|
||||||
SUPPORT_URL: 'https://support.edx.org',
|
|
||||||
ENABLE_ACCOUNT_DELETION: false,
|
|
||||||
ENABLE_COPPA_COMPLIANCE: false,
|
|
||||||
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
|
||||||
|
|
||||||
expect(screen.queryByText('We\'re sorry to see you go!')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import {
|
|
||||||
render, screen, fireEvent, waitFor,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import * as reactRedux from 'react-redux';
|
|
||||||
import DOBModal from '../DOBForm';
|
|
||||||
import messages from '../AccountSettingsPage.messages';
|
|
||||||
import { YEAR_OF_BIRTH_OPTIONS } from '../data/constants';
|
|
||||||
|
|
||||||
jest.mock('react-redux', () => ({
|
|
||||||
...jest.requireActual('react-redux'),
|
|
||||||
useDispatch: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/i18n', () => ({
|
|
||||||
...jest.requireActual('@edx/frontend-platform/i18n'),
|
|
||||||
useIntl: () => ({
|
|
||||||
formatMessage: (message) => message.defaultMessage,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@openedx/paragon', () => ({
|
|
||||||
...jest.requireActual('@openedx/paragon'),
|
|
||||||
Form: {
|
|
||||||
...jest.requireActual('@openedx/paragon').Form,
|
|
||||||
Control: {
|
|
||||||
...jest.requireActual('@openedx/paragon').Form.Control,
|
|
||||||
// eslint-disable-next-line react/prop-types
|
|
||||||
Feedback: ({ children, ...props }) => <div {...props}>{children}</div>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const mockStore = configureStore([]);
|
|
||||||
|
|
||||||
describe('DOBModal', () => {
|
|
||||||
let store;
|
|
||||||
let mockDispatch;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store = mockStore({
|
|
||||||
accountSettings: {
|
|
||||||
saveState: 'default',
|
|
||||||
errors: {},
|
|
||||||
openFormId: null,
|
|
||||||
confirmationValues: {},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
mockDispatch = jest.fn();
|
|
||||||
jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(mockDispatch); // ✅ replaced require with import
|
|
||||||
// Mock localStorage.setItem
|
|
||||||
Object.defineProperty(window, 'localStorage', {
|
|
||||||
value: {
|
|
||||||
setItem: jest.fn(),
|
|
||||||
},
|
|
||||||
writable: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderComponent = (props = {}) => render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<DOBModal
|
|
||||||
saveState="default"
|
|
||||||
error={undefined}
|
|
||||||
onSubmit={jest.fn()}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</Provider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
it('renders the modal with correct elements', async () => {
|
|
||||||
renderComponent();
|
|
||||||
const openButton = screen.getByTestId('open-modal-button');
|
|
||||||
expect(openButton).toHaveTextContent(messages['account.settings.field.dob.form.button'].defaultMessage);
|
|
||||||
|
|
||||||
fireEvent.click(openButton);
|
|
||||||
|
|
||||||
expect(screen.getByTestId('modal-title')).toHaveTextContent(messages['account.settings.field.dob.form.title'].defaultMessage);
|
|
||||||
expect(screen.getByTestId('help-text')).toHaveTextContent(messages['account.settings.field.dob.form.help.text'].defaultMessage);
|
|
||||||
expect(screen.getByTestId('month-label')).toHaveTextContent(messages['account.settings.field.dob.month'].defaultMessage);
|
|
||||||
expect(screen.getByTestId('year-label')).toHaveTextContent(messages['account.settings.field.dob.year'].defaultMessage);
|
|
||||||
expect(screen.getByTestId('month-select')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('year-select')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('cancel-button')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('submit-button')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('enables submit button when both month and year are selected', async () => {
|
|
||||||
renderComponent();
|
|
||||||
const openButton = screen.getByTestId('open-modal-button');
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.click(openButton);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
const monthSelect = screen.getByTestId('month-select');
|
|
||||||
const yearSelect = screen.getByTestId('year-select');
|
|
||||||
const submitButton = screen.getByTestId('submit-button');
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.change(monthSelect, { target: { value: '6' } });
|
|
||||||
fireEvent.change(yearSelect, { target: { value: YEAR_OF_BIRTH_OPTIONS[0].value } });
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(submitButton).not.toHaveAttribute('disabled');
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls onSubmit with correct data when form is submitted', async () => {
|
|
||||||
const mockOnSubmit = jest.fn();
|
|
||||||
renderComponent({ onSubmit: mockOnSubmit });
|
|
||||||
|
|
||||||
const openButton = screen.getByTestId('open-modal-button');
|
|
||||||
await act(async () => {
|
|
||||||
fireEvent.click(openButton);
|
|
||||||
});
|
|
||||||
await waitFor(() => {
|
|
||||||
const monthSelect = screen.getByTestId('month-select');
|
|
||||||
const yearSelect = screen.getByTestId('year-select');
|
|
||||||
const form = screen.getByTestId('dob-form');
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.change(monthSelect, { target: { value: '6' } });
|
|
||||||
fireEvent.change(yearSelect, { target: { value: '1990' } });
|
|
||||||
});
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
fireEvent.submit(form);
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(mockOnSubmit).toHaveBeenCalledWith('extended_profile', [
|
|
||||||
{ field_name: 'DOB', field_value: '1990-6' },
|
|
||||||
]);
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,184 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
render, screen, fireEvent, waitFor,
|
|
||||||
} from '@testing-library/react';
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import EditableField from '../EditableField';
|
|
||||||
import messages from '../AccountSettingsPage.messages';
|
|
||||||
|
|
||||||
jest.mock('../data/selectors', () => ({
|
|
||||||
editableFieldSelector: () => (state, props) => ({
|
|
||||||
...state.accountSettings,
|
|
||||||
isEditing: props.isEditing,
|
|
||||||
error: props.error || state.accountSettings.errors[props.name],
|
|
||||||
confirmationValue: props.confirmationValue || state.accountSettings.confirmationValues[props.name],
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../data/actions', () => ({
|
|
||||||
openForm: jest.fn((name) => ({ type: 'OPEN_FORM', payload: name })),
|
|
||||||
closeForm: jest.fn((name) => ({ type: 'CLOSE_FORM', payload: name })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// eslint-disable-next-line react/prop-types
|
|
||||||
jest.mock('../certificate-preference/CertificatePreference', () => function MockCertificatePreference({ fieldName }) {
|
|
||||||
return <div data-testid="editable-field-certificate-preference">Certificate Preference for {fieldName}</div>;
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockStore = configureStore([]);
|
|
||||||
const mockOnEdit = jest.fn();
|
|
||||||
const mockOnCancel = jest.fn();
|
|
||||||
const mockOnSubmit = jest.fn();
|
|
||||||
const mockOnChange = jest.fn();
|
|
||||||
|
|
||||||
const baseState = {
|
|
||||||
accountSettings: {
|
|
||||||
errors: {},
|
|
||||||
confirmationValues: {},
|
|
||||||
saveState: 'default',
|
|
||||||
openFormId: null,
|
|
||||||
verifiedNameHistory: { results: [] },
|
|
||||||
values: {},
|
|
||||||
drafts: {},
|
|
||||||
timeZones: [],
|
|
||||||
countryTimeZones: [],
|
|
||||||
thirdPartyAuth: { providers: [] },
|
|
||||||
countriesCodesList: [],
|
|
||||||
profileDataManager: false,
|
|
||||||
nameChangeModal: {},
|
|
||||||
loading: false,
|
|
||||||
loaded: true,
|
|
||||||
loadingError: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderComponent = (props = {}, stateOverrides = {}) => {
|
|
||||||
const store = mockStore({
|
|
||||||
...baseState,
|
|
||||||
...stateOverrides,
|
|
||||||
});
|
|
||||||
return render(
|
|
||||||
<Provider store={store}>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<EditableField
|
|
||||||
name="username"
|
|
||||||
label="Username"
|
|
||||||
type="text"
|
|
||||||
value="john_doe"
|
|
||||||
onEdit={mockOnEdit}
|
|
||||||
onCancel={mockOnCancel}
|
|
||||||
onSubmit={mockOnSubmit}
|
|
||||||
onChange={mockOnChange}
|
|
||||||
isEditing={false}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
</Provider>,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('EditableField', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders default state with value', () => {
|
|
||||||
renderComponent();
|
|
||||||
expect(screen.getByText('Username')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('john_doe')).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole('button', { name: /Edit/i })).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders empty label with edit button if no value and editable', () => {
|
|
||||||
renderComponent({ value: '', emptyLabel: 'Add value' });
|
|
||||||
expect(screen.getByRole('button', { name: 'Add value' })).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders empty label as muted text if not editable', () => {
|
|
||||||
renderComponent({ value: '', emptyLabel: 'No value', isEditable: false });
|
|
||||||
expect(screen.getByText('No value')).toHaveClass('text-muted');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders editing state with form controls', async () => {
|
|
||||||
renderComponent({ isEditing: true });
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('editable-field-textbox')).toHaveValue('john_doe');
|
|
||||||
expect(screen.getByTestId('editable-field-save')).toBeInTheDocument();
|
|
||||||
expect(screen.getByTestId('editable-field-cancel')).toBeInTheDocument();
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls onChange when input changes', async () => {
|
|
||||||
renderComponent({ isEditing: true });
|
|
||||||
await waitFor(() => {
|
|
||||||
const input = screen.getByTestId('editable-field-textbox');
|
|
||||||
fireEvent.change(input, { target: { value: 'new_name' } });
|
|
||||||
expect(mockOnChange).toHaveBeenCalledWith('username', 'new_name');
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('calls onSubmit when form is submitted', async () => {
|
|
||||||
renderComponent({ isEditing: true });
|
|
||||||
await waitFor(() => {
|
|
||||||
const form = screen.getByTestId('editable-field-form');
|
|
||||||
fireEvent.submit(form);
|
|
||||||
expect(mockOnSubmit).toHaveBeenCalledWith('username', 'john_doe');
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows error message when error is present', async () => {
|
|
||||||
const stateOverrides = {
|
|
||||||
accountSettings: {
|
|
||||||
...baseState.accountSettings,
|
|
||||||
errors: { username: 'Invalid input' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
renderComponent({ isEditing: true, error: 'Invalid input' }, stateOverrides);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('editable-field-error')).toHaveTextContent('Invalid input');
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows help text in editing mode', () => {
|
|
||||||
renderComponent({ isEditing: true, helpText: 'Helpful info' });
|
|
||||||
expect(screen.getByText('Helpful info')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows confirmation message in default mode if provided', async () => {
|
|
||||||
const stateOverrides = {
|
|
||||||
accountSettings: {
|
|
||||||
...baseState.accountSettings,
|
|
||||||
confirmationValues: { username: 'done' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
renderComponent(
|
|
||||||
{
|
|
||||||
confirmationMessageDefinition: messages['account.settings.editable.field.action.save'],
|
|
||||||
confirmationValue: 'done',
|
|
||||||
},
|
|
||||||
stateOverrides,
|
|
||||||
);
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('editable-field-confirmation')).toBeInTheDocument();
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders CertificatePreference for name fields when editing', async () => {
|
|
||||||
renderComponent({ isEditing: true, name: 'name' });
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(screen.getByTestId('editable-field-certificate-preference')).toHaveTextContent('Certificate Preference for name');
|
|
||||||
}, { timeout: 2000 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('applies grayed-out class when isGrayedOut is true', () => {
|
|
||||||
renderComponent({ isGrayedOut: true });
|
|
||||||
expect(screen.getByText('john_doe')).toHaveClass('grayed-out');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('appends userSuppliedValue when provided', () => {
|
|
||||||
renderComponent({ userSuppliedValue: 'extra' });
|
|
||||||
expect(screen.getByText('john_doe: extra')).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
import configureStore from 'redux-mock-store';
|
|
||||||
|
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import EditableSelectField from '../EditableSelectField';
|
|
||||||
|
|
||||||
const mockDispatch = jest.fn();
|
|
||||||
jest.mock('react-redux', () => ({
|
|
||||||
...jest.requireActual('react-redux'),
|
|
||||||
useDispatch: () => mockDispatch,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('@edx/frontend-platform/auth');
|
|
||||||
jest.mock('../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
|
||||||
|
|
||||||
const mockStore = configureStore();
|
|
||||||
|
|
||||||
describe('EditableSelectField', () => {
|
|
||||||
let props = {};
|
|
||||||
let store = {};
|
|
||||||
|
|
||||||
const reduxWrapper = children => (
|
|
||||||
<Router>
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<Provider store={store}>{children}</Provider>
|
|
||||||
</IntlProvider>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
store = mockStore();
|
|
||||||
props = {
|
|
||||||
name: 'testField',
|
|
||||||
label: 'Main Label',
|
|
||||||
emptyLabel: 'Empty Main Label',
|
|
||||||
type: 'text',
|
|
||||||
value: 'Test Field',
|
|
||||||
userSuppliedValue: '',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Default Option',
|
|
||||||
value: 'defaultOption',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'User Options',
|
|
||||||
group: [
|
|
||||||
{
|
|
||||||
label: 'Suboption 1',
|
|
||||||
value: 'suboption1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Other Options',
|
|
||||||
group: [
|
|
||||||
{
|
|
||||||
label: 'Suboption 2',
|
|
||||||
value: 'suboption2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Suboption 3',
|
|
||||||
value: 'suboption3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
saveState: 'default',
|
|
||||||
error: '',
|
|
||||||
confirmationMessageDefinition: {
|
|
||||||
id: 'confirmationMessageId',
|
|
||||||
defaultMessage: 'Default Confirmation Message',
|
|
||||||
description: 'Description of the confirmation message',
|
|
||||||
},
|
|
||||||
confirmationValue: 'Confirmation Value',
|
|
||||||
helpText: 'Helpful Text',
|
|
||||||
isEditing: false,
|
|
||||||
isEditable: true,
|
|
||||||
isGrayedOut: false,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => jest.clearAllMocks());
|
|
||||||
|
|
||||||
it('renders EditableSelectField correctly with editing disabled', () => {
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...props} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders EditableSelectField correctly with editing enabled', () => {
|
|
||||||
props = {
|
|
||||||
...props,
|
|
||||||
isEditing: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...props} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders EditableSelectField with an error', () => {
|
|
||||||
const errorProps = {
|
|
||||||
...props,
|
|
||||||
error: 'This is an error message',
|
|
||||||
};
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...errorProps} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders selectOptions when option has a group', () => {
|
|
||||||
const propsWithGroup = {
|
|
||||||
...props,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'User Options',
|
|
||||||
group: [
|
|
||||||
{
|
|
||||||
label: 'Suboption 1',
|
|
||||||
value: 'suboption1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithGroup} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders selectOptions when option does not have a group', () => {
|
|
||||||
const propsWithoutGroup = {
|
|
||||||
...props,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Default Option',
|
|
||||||
value: 'defaultOption',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithoutGroup} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders selectOptions with multiple groups', () => {
|
|
||||||
const propsWithGroups = {
|
|
||||||
...props,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Mixed Options',
|
|
||||||
group: [
|
|
||||||
{
|
|
||||||
label: 'Suboption 1',
|
|
||||||
value: 'suboption1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Suboption 2',
|
|
||||||
value: 'suboption2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithGroups} />)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,64 +1,62 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import React from 'react';
|
||||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
import renderer from 'react-test-renderer';
|
||||||
import { AppProvider } from '@edx/frontend-platform/react';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { initializeMockApp, mergeConfig, setConfig } from '@edx/frontend-platform';
|
|
||||||
|
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { mergeConfig, setConfig } from '@edx/frontend-platform';
|
||||||
import JumpNav from '../JumpNav';
|
import JumpNav from '../JumpNav';
|
||||||
import configureStore from '../../data/configureStore';
|
|
||||||
|
const IntlJumpNav = injectIntl(JumpNav);
|
||||||
|
|
||||||
describe('JumpNav', () => {
|
describe('JumpNav', () => {
|
||||||
mergeConfig({
|
mergeConfig({
|
||||||
ENABLE_ACCOUNT_DELETION: true,
|
ENABLE_DEMOGRAPHICS_COLLECTION: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let store;
|
let props = {};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
initializeMockApp({
|
props = {
|
||||||
authenticatedUser: {
|
intl: {},
|
||||||
userId: 3,
|
displayDemographicsLink: false,
|
||||||
username: 'abc123',
|
};
|
||||||
administrator: true,
|
|
||||||
roles: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
store = configureStore({
|
|
||||||
notificationPreferences: {
|
|
||||||
showPreferences: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render delete account link', async () => {
|
it('should not render Optional Information link', () => {
|
||||||
setConfig({
|
const tree = renderer.create((
|
||||||
ENABLE_ACCOUNT_DELETION: false,
|
// Had to wrap the following in a router or I will receive an error stating:
|
||||||
});
|
// "Invariant failed: You should not use <NavLink> outside a <Router>"
|
||||||
|
<Router>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<IntlJumpNav {...props} />
|
||||||
|
</IntlProvider>
|
||||||
|
</Router>
|
||||||
|
))
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
render(
|
expect(tree).toMatchSnapshot();
|
||||||
<IntlProvider locale="en">
|
|
||||||
<AppProvider store={store}>
|
|
||||||
<JumpNav />
|
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await screen.queryByText('Delete My Account')).toBeNull();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render delete account link', async () => {
|
it('should render Optional Information link', () => {
|
||||||
setConfig({
|
setConfig({
|
||||||
ENABLE_ACCOUNT_DELETION: true,
|
ENABLE_DEMOGRAPHICS_COLLECTION: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
render(
|
props = {
|
||||||
<IntlProvider locale="en">
|
...props,
|
||||||
<AppProvider store={store}>
|
displayDemographicsLink: true,
|
||||||
<JumpNav />
|
};
|
||||||
</AppProvider>
|
|
||||||
</IntlProvider>,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(await screen.findByText('Delete My Account')).toBeVisible();
|
const tree = renderer.create((
|
||||||
|
// Same as previous test
|
||||||
|
<Router>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<IntlJumpNav {...props} />
|
||||||
|
</IntlProvider>
|
||||||
|
</Router>
|
||||||
|
))
|
||||||
|
.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,483 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders EditableSelectField correctly with editing disabled 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-start"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
aria-level="3"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</h6>
|
|
||||||
<button
|
|
||||||
className="ml-3 btn btn-link"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-pencil mr-1"
|
|
||||||
data-icon="pencil"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1 0 32c0 8.8 7.2 16 16 16l32 0zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={null}
|
|
||||||
data-hj-suppress={true}
|
|
||||||
>
|
|
||||||
Test Field
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="small text-muted mt-n2"
|
|
||||||
>
|
|
||||||
Default Confirmation Message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders EditableSelectField correctly with editing enabled 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
onSubmit={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="pgn__form-group"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
className="pgn__form-label h6 d-block"
|
|
||||||
htmlFor="field-testField"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-decorator-group"
|
|
||||||
>
|
|
||||||
<text
|
|
||||||
aria-describedby="field-testField-2"
|
|
||||||
className="has-value form-control is-invalid"
|
|
||||||
data-hj-suppress={true}
|
|
||||||
id="field-testField"
|
|
||||||
name="testField"
|
|
||||||
onBlur={[Function]}
|
|
||||||
onChange={[Function]}
|
|
||||||
type="text"
|
|
||||||
value="Test Field"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="defaultOption"
|
|
||||||
>
|
|
||||||
Default Option
|
|
||||||
</option>
|
|
||||||
<optgroup
|
|
||||||
label="User Options"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="suboption1"
|
|
||||||
>
|
|
||||||
Suboption 1
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
<optgroup
|
|
||||||
label="Other Options"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="suboption2"
|
|
||||||
>
|
|
||||||
Suboption 2
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
value="suboption3"
|
|
||||||
>
|
|
||||||
Suboption 3
|
|
||||||
</option>
|
|
||||||
</optgroup>
|
|
||||||
</text>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="pgn__form-text pgn__form-text-default"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
Helpful Text
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
|
||||||
id="field-testField-2"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="pgn__icon"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden={true}
|
|
||||||
fill="none"
|
|
||||||
focusable={false}
|
|
||||||
height={24}
|
|
||||||
role="img"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
width={24}
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<div />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<button
|
|
||||||
aria-disabled={false}
|
|
||||||
aria-live="assertive"
|
|
||||||
className="pgn__stateful-btn pgn__stateful-btn-state-default mr-2 btn btn-primary"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="d-flex align-items-center justify-content-center"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Save
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline-primary"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders EditableSelectField with an error 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-start"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
aria-level="3"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</h6>
|
|
||||||
<button
|
|
||||||
className="ml-3 btn btn-link"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-pencil mr-1"
|
|
||||||
data-icon="pencil"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1 0 32c0 8.8 7.2 16 16 16l32 0zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={null}
|
|
||||||
data-hj-suppress={true}
|
|
||||||
>
|
|
||||||
Test Field
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="small text-muted mt-n2"
|
|
||||||
>
|
|
||||||
Default Confirmation Message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders selectOptions when option does not have a group 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-start"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
aria-level="3"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</h6>
|
|
||||||
<button
|
|
||||||
className="ml-3 btn btn-link"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-pencil mr-1"
|
|
||||||
data-icon="pencil"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1 0 32c0 8.8 7.2 16 16 16l32 0zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={null}
|
|
||||||
data-hj-suppress={true}
|
|
||||||
>
|
|
||||||
Test Field
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="small text-muted mt-n2"
|
|
||||||
>
|
|
||||||
Default Confirmation Message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders selectOptions when option has a group 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-start"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
aria-level="3"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</h6>
|
|
||||||
<button
|
|
||||||
className="ml-3 btn btn-link"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-pencil mr-1"
|
|
||||||
data-icon="pencil"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1 0 32c0 8.8 7.2 16 16 16l32 0zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={null}
|
|
||||||
data-hj-suppress={true}
|
|
||||||
>
|
|
||||||
Test Field
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="small text-muted mt-n2"
|
|
||||||
>
|
|
||||||
Default Confirmation Message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`EditableSelectField renders selectOptions with multiple groups 1`] = `
|
|
||||||
<div
|
|
||||||
className="pgn-transition-replace-group position-relative"
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"height": null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"padding": ".1px 0",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="form-group"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="d-flex align-items-start"
|
|
||||||
>
|
|
||||||
<h6
|
|
||||||
aria-level="3"
|
|
||||||
>
|
|
||||||
Main Label
|
|
||||||
</h6>
|
|
||||||
<button
|
|
||||||
className="ml-3 btn btn-link"
|
|
||||||
disabled={false}
|
|
||||||
onClick={[Function]}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
className="svg-inline--fa fa-pencil mr-1"
|
|
||||||
data-icon="pencil"
|
|
||||||
data-prefix="fas"
|
|
||||||
focusable="false"
|
|
||||||
role="img"
|
|
||||||
style={{}}
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M410.3 231l11.3-11.3-33.9-33.9-62.1-62.1L291.7 89.8l-11.3 11.3-22.6 22.6L58.6 322.9c-10.4 10.4-18 23.3-22.2 37.4L1 480.7c-2.5 8.4-.2 17.5 6.1 23.7s15.3 8.5 23.7 6.1l120.3-35.4c14.1-4.2 27-11.8 37.4-22.2L387.7 253.7 410.3 231zM160 399.4l-9.1 22.7c-4 3.1-8.5 5.4-13.3 6.9L59.4 452l23-78.1c1.4-4.9 3.8-9.4 6.9-13.3l22.7-9.1 0 32c0 8.8 7.2 16 16 16l32 0zM362.7 18.7L348.3 33.2 325.7 55.8 314.3 67.1l33.9 33.9 62.1 62.1 33.9 33.9 11.3-11.3 22.6-22.6 14.5-14.5c25-25 25-65.5 0-90.5L453.3 18.7c-25-25-65.5-25-90.5 0zm-47.4 168l-144 144c-6.2 6.2-16.4 6.2-22.6 0s-6.2-16.4 0-22.6l144-144c6.2-6.2 16.4-6.2 22.6 0s6.2 16.4 0 22.6z"
|
|
||||||
fill="currentColor"
|
|
||||||
style={{}}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
className={null}
|
|
||||||
data-hj-suppress={true}
|
|
||||||
>
|
|
||||||
Test Field
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="small text-muted mt-n2"
|
|
||||||
>
|
|
||||||
Default Confirmation Message
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user