Compare commits
405 Commits
open-relea
...
split-full
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3467534bc7 | ||
|
|
5f43f945bb | ||
|
|
0ee0f41f3e | ||
|
|
35b66ae38b | ||
|
|
361e14c980 | ||
|
|
8e967fa3bf | ||
|
|
67feee5e0b | ||
|
|
837ac4e635 | ||
|
|
5c6ddd2888 | ||
|
|
7ef1b5b92d | ||
|
|
672d39f99c | ||
|
|
08a0d8e30b | ||
|
|
208c7a1ada | ||
|
|
afe2a754bd | ||
|
|
594ffe4aa9 | ||
|
|
8ecdedcdc8 | ||
|
|
11e144dec0 | ||
|
|
c71e586e64 | ||
|
|
d0d2aeed71 | ||
|
|
737833cdeb | ||
|
|
c00ebc9f64 | ||
|
|
41ee7e9538 | ||
|
|
b7181d5643 | ||
|
|
50e0235a6a | ||
|
|
5166546048 | ||
|
|
49d3acd1a1 | ||
|
|
cdf799d515 | ||
|
|
ed26467f0d | ||
|
|
fea3991915 | ||
|
|
30ca5f577e | ||
|
|
4047c3c923 | ||
|
|
3d32c8624d | ||
|
|
0c70e31655 | ||
|
|
10e1a451f7 | ||
|
|
7f2d3700d7 | ||
|
|
eb88d9b46d | ||
|
|
26dbac709f | ||
|
|
d6592a77f9 | ||
|
|
91d0feaed9 | ||
|
|
237599c830 | ||
|
|
56d0ad3db9 | ||
|
|
c0b504763e | ||
|
|
4aa44b89a3 | ||
|
|
be661b8a27 | ||
|
|
3c75052d8b | ||
|
|
229a674469 | ||
|
|
2c75c7f790 | ||
|
|
d595986d0e | ||
|
|
43d8784014 | ||
|
|
f15b71d20c | ||
|
|
aae79c45bb | ||
|
|
5233c6aa59 | ||
|
|
d59c9e38bd | ||
|
|
eb35897bf7 | ||
|
|
c28b8c6840 | ||
|
|
1698720aad | ||
|
|
57a2c7bcb6 | ||
|
|
f6f67a1a80 | ||
|
|
aec7e1281f | ||
|
|
cdf0ee5e4e | ||
|
|
f0858b7381 | ||
|
|
5f49bedaa8 | ||
|
|
a94f472459 | ||
|
|
2fc2d86a44 | ||
|
|
899f88ca1e | ||
|
|
399dd04df6 | ||
|
|
46dd8739ab | ||
|
|
5de18e0dba | ||
|
|
ca3bc9151c | ||
|
|
9cccf09394 | ||
|
|
ced29278d5 | ||
|
|
bc8197a9af | ||
|
|
f86677171f | ||
|
|
966b59b70f | ||
|
|
0e6aff7c6f | ||
|
|
13dbb732e3 | ||
|
|
b80748aa4c | ||
|
|
f098fe1a3a | ||
|
|
a989fabb92 | ||
|
|
1257e81781 | ||
|
|
b08890b794 | ||
|
|
7f1c7b86ef | ||
|
|
a5fd3a7f7e | ||
|
|
4513cc8834 | ||
|
|
40103a2386 | ||
|
|
b6c18bb439 | ||
|
|
ee51939f2d | ||
|
|
fc127ccd98 | ||
|
|
22faebac50 | ||
|
|
5ce3995f5b | ||
|
|
e181269703 | ||
|
|
5d212ec6b5 | ||
|
|
363096c4f0 | ||
|
|
85c5902559 | ||
|
|
fca6da2df7 | ||
|
|
7a74e5d29b | ||
|
|
4b94344dd9 | ||
|
|
5245254de5 | ||
|
|
afda76ff11 | ||
|
|
da52ddd35a | ||
|
|
bb2aef3878 | ||
|
|
4d6f76d9b3 | ||
|
|
869e083a2a | ||
|
|
ee876b5c84 | ||
|
|
a2b25449de | ||
|
|
16218252f1 | ||
|
|
757e446be7 | ||
|
|
db0f8f80bc | ||
|
|
49bc817f2d | ||
|
|
c31beeef96 | ||
|
|
09970d7935 | ||
|
|
d15b0baf74 | ||
|
|
b9802a130e | ||
|
|
7c0ea75e21 | ||
|
|
e1b02de7de | ||
|
|
6c4dbc5db0 | ||
|
|
d180626122 | ||
|
|
a9518b7388 | ||
|
|
51b18e9c52 | ||
|
|
3d98558bf6 | ||
|
|
e7e7f518bf | ||
|
|
cb06c8778a | ||
|
|
ab5c205a7f | ||
|
|
2f85902d2c | ||
|
|
b75e78bdda | ||
|
|
182a0251a4 | ||
|
|
89881c64a6 | ||
|
|
7321e2a159 | ||
|
|
9b45aa3bc9 | ||
|
|
dfadac08d3 | ||
|
|
2e87f0bd9f | ||
|
|
7f8086545c | ||
|
|
de6e3c2010 | ||
|
|
eb6d0125c6 | ||
|
|
e8f754c10b | ||
|
|
a72bbf2f58 | ||
|
|
c465f51e66 | ||
|
|
599e658742 | ||
|
|
929a669cad | ||
|
|
503b8b5176 | ||
|
|
d2aa727c12 | ||
|
|
d66dcecd2f | ||
|
|
80d0c44b40 | ||
|
|
0e5cd30d01 | ||
|
|
56b9fe3998 | ||
|
|
a22f1298eb | ||
|
|
b7bd6a2846 | ||
|
|
a52ab171de | ||
|
|
13a7508f26 | ||
|
|
a499aa4cc5 | ||
|
|
e7207878d4 | ||
|
|
47d64c1cf5 | ||
|
|
c75dc86263 | ||
|
|
105a8d1a3c | ||
|
|
c74cf52e38 | ||
|
|
5c9b448b14 | ||
|
|
4e95495dcc | ||
|
|
82f86f5fbe | ||
|
|
f737d6e158 | ||
|
|
f9feb94668 | ||
|
|
112afa7e51 | ||
|
|
1694ea38ab | ||
|
|
0f3b7caa0f | ||
|
|
a85e1e1e15 | ||
|
|
55d00027a9 | ||
|
|
bf012619a6 | ||
|
|
f47795bf40 | ||
|
|
848e7a2f85 | ||
|
|
ecc6138833 | ||
|
|
d6600eb876 | ||
|
|
bc237de755 | ||
|
|
89abb51734 | ||
|
|
be8570edac | ||
|
|
09cce6802d | ||
|
|
c164dd7dcf | ||
|
|
00435fb27d | ||
|
|
0ebaa0b991 | ||
|
|
0d7b529233 | ||
|
|
2c2f7e8e98 | ||
|
|
768b8a2417 | ||
|
|
192714629c | ||
|
|
f2f761e8db | ||
|
|
410aa14b28 | ||
|
|
e857293414 | ||
|
|
492f911930 | ||
|
|
0002d84a6c | ||
|
|
9b49b38496 | ||
|
|
7cf9294e09 | ||
|
|
129f73aa4f | ||
|
|
7bdb93784b | ||
|
|
b32c001fec | ||
|
|
5f134d7b99 | ||
|
|
81b0823632 | ||
|
|
95e3af7487 | ||
|
|
77047bab2a | ||
|
|
c1cf6d65de | ||
|
|
f626fc2f89 | ||
|
|
f56b1b4530 | ||
|
|
1c423c4b6c | ||
|
|
7715b143d2 | ||
|
|
b200417903 | ||
|
|
138d80d57d | ||
|
|
07a33447ad | ||
|
|
4d11d28f96 | ||
|
|
e61dc12eab | ||
|
|
2c268d906c | ||
|
|
0acaebe067 | ||
|
|
9ea109f705 | ||
|
|
be2964562f | ||
|
|
6d7bf1b878 | ||
|
|
3a7c963f3c | ||
|
|
3728928a6d | ||
|
|
596eeee59d | ||
|
|
ae2f8a384f | ||
|
|
ffe0989969 | ||
|
|
53eeb26e28 | ||
|
|
0325d2a7f6 | ||
|
|
b83d568dfb | ||
|
|
2f839b3362 | ||
|
|
bfc655d6ab | ||
|
|
bd63ea9484 | ||
|
|
6c825625fb | ||
|
|
be59c2da90 | ||
|
|
8a4e6730a8 | ||
|
|
6053475c6f | ||
|
|
ed1cfc28aa | ||
|
|
1637aea0fa | ||
|
|
3c5aa05b48 | ||
|
|
5a1dc5a992 | ||
|
|
ccbf8201a0 | ||
|
|
34be9f681a | ||
|
|
cfca229500 | ||
|
|
d2396a8162 | ||
|
|
b630514906 | ||
|
|
f8dbbf7757 | ||
|
|
8eb26db72d | ||
|
|
499cbeaa62 | ||
|
|
4b103161b9 | ||
|
|
fb8e4829c3 | ||
|
|
4a81220aa3 | ||
|
|
85a0d751c0 | ||
|
|
859195be94 | ||
|
|
8499002072 | ||
|
|
6e85a2e1d9 | ||
|
|
e4b4197a9a | ||
|
|
60f34bf2ae | ||
|
|
63afc7d7be | ||
|
|
0aec952fd2 | ||
|
|
f8b8374058 | ||
|
|
d62bb11a9e | ||
|
|
af39ecbc5f | ||
|
|
6c2330fc6c | ||
|
|
109da5fa38 | ||
|
|
81ac063cf8 | ||
|
|
50f2442512 | ||
|
|
be28f398d6 | ||
|
|
cd47862c1d | ||
|
|
847e3e05b0 | ||
|
|
e347b3fb23 | ||
|
|
a6a1d94d92 | ||
|
|
e54de547f6 | ||
|
|
574cfb91c2 | ||
|
|
5bdf0a4978 | ||
|
|
cd8dee1731 | ||
|
|
0e7d2c048d | ||
|
|
79c4a14f6f | ||
|
|
de8977347a | ||
|
|
783f78a9ef | ||
|
|
c1fa6efb30 | ||
|
|
b3cd370d8e | ||
|
|
8cb416d142 | ||
|
|
dbd4faf558 | ||
|
|
92c8f17e2a | ||
|
|
c858966035 | ||
|
|
547d55a31f | ||
|
|
f78420511e | ||
|
|
e3ad6e6e54 | ||
|
|
c6ffc51a5d | ||
|
|
9b28469afd | ||
|
|
2bb1388a45 | ||
|
|
bf0d1379f6 | ||
|
|
0d9bc5988d | ||
|
|
3a2075de9c | ||
|
|
ef6239a807 | ||
|
|
8cb9df6d89 | ||
|
|
fa856b54bf | ||
|
|
e9f6acff27 | ||
|
|
dc26da9cc1 | ||
|
|
9dd05761fd | ||
|
|
0d5b86df57 | ||
|
|
d8eb6e9da5 | ||
|
|
d280062fe3 | ||
|
|
f0366a98f4 | ||
|
|
9d0577fd93 | ||
|
|
dba70b557c | ||
|
|
f6840bc202 | ||
|
|
1af5f0d179 | ||
|
|
b3a91470f8 | ||
|
|
9b18588e26 | ||
|
|
3d2e7dc8a4 | ||
|
|
41f1552f9b | ||
|
|
ffa8a36ce6 | ||
|
|
3505e645e3 | ||
|
|
88db6084df | ||
|
|
27c8ab28f3 | ||
|
|
e01b595a0c | ||
|
|
0d351f5173 | ||
|
|
03dbfb5670 | ||
|
|
1e17fc9e32 | ||
|
|
da912f1e35 | ||
|
|
fb0f832832 | ||
|
|
870dd631bb | ||
|
|
5b9f251e93 | ||
|
|
98c14bbd3b | ||
|
|
00ce8b927f | ||
|
|
ab215cd909 | ||
|
|
51e7773207 | ||
|
|
6f5a1a8aa9 | ||
|
|
deb48fb9d2 | ||
|
|
20b451afb6 | ||
|
|
ad6f812974 | ||
|
|
9573516b37 | ||
|
|
f0d6a92ab2 | ||
|
|
dc4d4031e9 | ||
|
|
84a5c7aaf1 | ||
|
|
6ad666342d | ||
|
|
1252498872 | ||
|
|
f9d04e4dd4 | ||
|
|
c96b9bb77d | ||
|
|
f3c672c5ae | ||
|
|
63c396c03a | ||
|
|
0981857062 | ||
|
|
0527c73529 | ||
|
|
5c5dbc369b | ||
|
|
196719963f | ||
|
|
92ee4dfbb9 | ||
|
|
9d0b3524cb | ||
|
|
c39fd332b6 | ||
|
|
04d515f554 | ||
|
|
f425e9b94f | ||
|
|
7ec147fe6f | ||
|
|
7a8ae85b72 | ||
|
|
0f8f5a1e9a | ||
|
|
757a9ac033 | ||
|
|
9efc8d1290 | ||
|
|
5f53270148 | ||
|
|
9716495951 | ||
|
|
f9b29948e7 | ||
|
|
bededb3912 | ||
|
|
bb6390f9ae | ||
|
|
d2300d2dfd | ||
|
|
a501407907 | ||
|
|
88e63cd390 | ||
|
|
eaebe6980b | ||
|
|
d6efba63ca | ||
|
|
257e425fd9 | ||
|
|
02e3364874 | ||
|
|
9ae74708fb | ||
|
|
e946e377c6 | ||
|
|
5dbe649b2c | ||
|
|
2792902975 | ||
|
|
6f643070ea | ||
|
|
ce946f56b2 | ||
|
|
de1e67f68d | ||
|
|
79001bccd8 | ||
|
|
963884cc4c | ||
|
|
e43c1bcc9e | ||
|
|
32cc2c7835 | ||
|
|
5c39c279f3 | ||
|
|
12bca9b771 | ||
|
|
5e10c2bc18 | ||
|
|
f2fe22b8f7 | ||
|
|
d5601a21fd | ||
|
|
f3bd7a8589 | ||
|
|
7643bbd6ba | ||
|
|
fd1044b531 | ||
|
|
f25e5db422 | ||
|
|
0d569a060b | ||
|
|
350016cbe0 | ||
|
|
1619dade50 | ||
|
|
dafc34f535 | ||
|
|
9bbab2620c | ||
|
|
81bef65cc2 | ||
|
|
a18daecf8a | ||
|
|
449e4c0253 | ||
|
|
a66ba187ae | ||
|
|
2d710f7060 | ||
|
|
e109e5018e | ||
|
|
1444831833 | ||
|
|
4b4f29ae19 | ||
|
|
cbc4123e78 | ||
|
|
2c896f77d4 | ||
|
|
112ddf80e6 | ||
|
|
6f2a69acc1 | ||
|
|
d2c83b82f7 | ||
|
|
03501a8125 | ||
|
|
6e17214476 | ||
|
|
2c6cec7f8c | ||
|
|
f76797cade | ||
|
|
59325bd412 | ||
|
|
65a6bc5002 | ||
|
|
eff28d8b47 | ||
|
|
dfb13c4286 | ||
|
|
aada46f6eb | ||
|
|
9e967ba1ea |
10
.env
10
.env
@@ -1,6 +1,5 @@
|
||||
ACCESS_TOKEN_COOKIE_NAME=''
|
||||
BASE_URL=''
|
||||
COACHING_ENABLED=''
|
||||
CREDENTIALS_BASE_URL=''
|
||||
CSRF_TOKEN_API_PATH=''
|
||||
DEMOGRAPHICS_BASE_URL=''
|
||||
@@ -25,3 +24,12 @@ SITE_NAME=''
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL=''
|
||||
USER_INFO_COOKIE_NAME=''
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_ACCOUNT_DELETION=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
PASSWORD_RESET_SUPPORT_LINK=''
|
||||
LEARNER_FEEDBACK_URL=''
|
||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://support.edx.org/hc/en-us/articles/207206067'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||
BASE_URL='localhost:1997'
|
||||
COACHING_ENABLED=''
|
||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
||||
@@ -26,3 +25,12 @@ SITE_NAME=localhost
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_ACCOUNT_DELETION=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
PASSWORD_RESET_SUPPORT_LINK='mailto:support@example.com'
|
||||
LEARNER_FEEDBACK_URL=''
|
||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://support.edx.org/hc/en-us/articles/207206067'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||
BASE_URL='localhost:1997'
|
||||
COACHING_ENABLED=''
|
||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
||||
@@ -25,3 +24,11 @@ SITE_NAME=localhost
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_ACCOUNT_DELETION=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
LEARNER_FEEDBACK_URL=''
|
||||
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://support.edx.org/hc/en-us/articles/207206067'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
|
||||
module.exports = createConfig('eslint');
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
||||
* @edx/community-engineering
|
||||
19
.github/workflows/add-depr-ticket-to-depr-board.yml
vendored
Normal file
19
.github/workflows/add-depr-ticket-to-depr-board.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Run the workflow that adds new tickets that are either:
|
||||
# - labelled "DEPR"
|
||||
# - title starts with "[DEPR]"
|
||||
# - body starts with "Proposal Date" (this is the first template field)
|
||||
# to the org-wide DEPR project board
|
||||
|
||||
name: Add newly created DEPR issues to the DEPR project board
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
routeissue:
|
||||
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
|
||||
secrets:
|
||||
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
||||
20
.github/workflows/add-remove-label-on-comment.yml
vendored
Normal file
20
.github/workflows/add-remove-label-on-comment.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# This workflow runs when a comment is made on the ticket
|
||||
# If the comment starts with "label: " it tries to apply
|
||||
# the label indicated in rest of comment.
|
||||
# If the comment starts with "remove label: ", it tries
|
||||
# to remove the indicated label.
|
||||
# Note: Labels are allowed to have spaces and this script does
|
||||
# not parse spaces (as often a space is legitimate), so the command
|
||||
# "label: really long lots of words label" will apply the
|
||||
# label "really long lots of words label"
|
||||
|
||||
name: Allows for the adding and removing of labels via comment
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
add_remove_labels:
|
||||
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master
|
||||
|
||||
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@@ -1,4 +1,3 @@
|
||||
---
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
@@ -6,27 +5,25 @@ on:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version:
|
||||
- 12
|
||||
npm-test:
|
||||
- i18n_extract
|
||||
- is-es5
|
||||
- lint
|
||||
- test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Nodejs Env
|
||||
run: echo "NODE_VER=`cat .nvmrc`" >> $GITHUB_ENV
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install -g npm@6
|
||||
node-version: ${{ env.NODE_VER }}
|
||||
- run: make requirements
|
||||
- run: make test NPM_TESTS=build
|
||||
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
||||
- name: upload coverage
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
|
||||
2
.github/workflows/commitlint.yml
vendored
2
.github/workflows/commitlint.yml
vendored
@@ -7,4 +7,4 @@ on:
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
uses: edx/.github/.github/workflows/commitlint.yml@master
|
||||
uses: openedx/.github/.github/workflows/commitlint.yml@master
|
||||
|
||||
14
.github/workflows/lockfileversion-check.yml
vendored
Normal file
14
.github/workflows/lockfileversion-check.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
#check package-lock file version
|
||||
|
||||
name: Lockfile Version check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: openedx/.github/.github/workflows/lockfile-check.yml@master
|
||||
|
||||
12
.github/workflows/self-assign-issue.yml
vendored
Normal file
12
.github/workflows/self-assign-issue.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# This workflow runs when a comment is made on the ticket
|
||||
# If the comment starts with "assign me" it assigns the author to the
|
||||
# ticket (case insensitive)
|
||||
|
||||
name: Assign comment author to ticket if they say "assign me"
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
self_assign_by_comment:
|
||||
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master
|
||||
12
.github/workflows/update-browserslist-db.yml
vendored
Normal file
12
.github/workflows/update-browserslist-db.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: Update Browserslist DB
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-browserslist:
|
||||
uses: openedx/.github/.github/workflows/update-browserslist-db.yml@master
|
||||
|
||||
secrets:
|
||||
requirements_bot_github_token: ${{ secrets.requirements_bot_github_token }}
|
||||
@@ -1,8 +1,9 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[edx-platform.frontend-app-account]
|
||||
[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
|
||||
type = KEYVALUEJSON
|
||||
|
||||
|
||||
34
Makefile
Executable file → Normal file
34
Makefile
Executable file → Normal file
@@ -1,16 +1,16 @@
|
||||
export TRANSIFEX_RESOURCE = frontend-app-account
|
||||
transifex_resource = frontend-app-account
|
||||
transifex_langs = "ar,fr,es_419,zh_CN"
|
||||
transifex_langs = "ar,de,es_419,fa_IR,fr,fr_CA,hi,it,pt,ru,uk,zh_CN,it_IT,pt_PT,de_DE"
|
||||
|
||||
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||
i18n = ./src/i18n
|
||||
transifex_input = $(i18n)/transifex_input.json
|
||||
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
|
||||
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
|
||||
|
||||
# This directory must match .babelrc .
|
||||
transifex_temp = ./temp/babel-plugin-react-intl
|
||||
transifex_temp = ./temp/babel-plugin-formatjs
|
||||
|
||||
NPM_TESTS=build i18n_extract lint test is-es5
|
||||
NPM_TESTS=build i18n_extract lint test
|
||||
|
||||
.PHONY: test
|
||||
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
|
||||
@@ -45,15 +45,31 @@ push_translations:
|
||||
# Pushing strings to Transifex...
|
||||
tx push -s
|
||||
# Fetching hashes from Transifex...
|
||||
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
|
||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||
# Writing out comments to file...
|
||||
$(transifex_utils) $(transifex_temp) --comments
|
||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||
# Pushing comments to Transifex...
|
||||
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
|
||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||
|
||||
ifeq ($(OPENEDX_ATLAS_PULL),)
|
||||
# Pulls translations from Transifex.
|
||||
pull_translations:
|
||||
tx pull -f --mode reviewed --language=$(transifex_langs)
|
||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
||||
else
|
||||
# Experimental: OEP-58 Pulls translations using atlas
|
||||
pull_translations:
|
||||
rm -rf src/i18n/messages
|
||||
mkdir src/i18n/messages
|
||||
cd src/i18n/messages \
|
||||
&& atlas pull $(ATLAS_OPTIONS) \
|
||||
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||
translations/paragon/src/i18n/messages:paragon \
|
||||
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
||||
translations/frontend-app-account/src/i18n/messages:frontend-app-account
|
||||
|
||||
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-app-account
|
||||
endif
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
|
||||
159
README.rst
159
README.rst
@@ -1,18 +1,19 @@
|
||||
####################
|
||||
frontend-app-account
|
||||
####################
|
||||
|
||||
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||
|
||||
frontend-app-account
|
||||
====================
|
||||
|
||||
Please tag **@edx/community-engineering** on any PRs or issues. Thanks!
|
||||
|
||||
Introduction
|
||||
------------
|
||||
********
|
||||
Purpose
|
||||
********
|
||||
|
||||
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
||||
|
||||
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/edx/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
|
||||
|
||||
@@ -20,12 +21,32 @@ In this MFE: Private user settings UIs. Public facing profile is in a `separate
|
||||
|
||||
- IDV (Identity Verification)
|
||||
|
||||
***************
|
||||
Getting Started
|
||||
***************
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
|
||||
The `devstack`_ is currently recommended as a development environment for your
|
||||
new MFE. If you start it with ``make dev.up.lms`` that should give you
|
||||
everything you need as a companion to this frontend.
|
||||
|
||||
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
|
||||
to the `relevant tutor-mfe documentation`_ to get started using it.
|
||||
|
||||
.. _Devstack: https://github.com/openedx/devstack
|
||||
|
||||
.. _Tutor: https://github.com/overhangio/tutor
|
||||
|
||||
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
|
||||
|
||||
Installation
|
||||
------------
|
||||
============
|
||||
|
||||
This MFE is bundled with `Devstack <https://github.com/edx/devstack>`_, see the `Getting Started <https://github.com/edx/devstack#getting-started>`_ section for setup instructions.
|
||||
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.
|
||||
|
||||
1. Install Devstack using the `Getting Started <https://github.com/edx/devstack#getting-started>`_ instructions.
|
||||
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
|
||||
|
||||
2. Start up Devstack, if it's not already started.
|
||||
|
||||
@@ -43,7 +64,7 @@ This MFE is bundled with `Devstack <https://github.com/edx/devstack>`_, see the
|
||||
.. image:: ./docs/images/localhost_preview.png
|
||||
|
||||
Environment Variables/Setup Notes
|
||||
---------------------------------
|
||||
=================================
|
||||
|
||||
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||
|
||||
@@ -55,17 +76,28 @@ Example: ``https://support.example.com``
|
||||
|
||||
The fully-qualified URL to the support page in the target environment.
|
||||
|
||||
``PASSWORD_RESET_SUPPORT_LINK``
|
||||
|
||||
Examples:
|
||||
|
||||
- ``https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-``
|
||||
|
||||
- ``mailto:support@example.com``
|
||||
|
||||
The fully-qualified URL to the support page or email to request the support from in the target environment.
|
||||
|
||||
``ENABLE_ACCOUNT_DELETION``
|
||||
|
||||
Example: ``'false'`` | ``''`` (empty strings are true)
|
||||
|
||||
Enable the account deletion option, defaults to true.
|
||||
To disable account deletion set ``ENABLE_ACCOUNT_DELETION`` to ``'false'`` (string), otherwise it will default to true.
|
||||
|
||||
edX-specific Environment Variables
|
||||
**********************************
|
||||
==================================
|
||||
|
||||
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and are unsupported in Open edX. Enabling these environment variables will result in undefined behavior in Open edX installations:
|
||||
|
||||
``COACHING_ENABLED``
|
||||
|
||||
Example: ``true`` | ``''`` (empty strings are falsy)
|
||||
|
||||
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_DEMOGRAPHICS_COLLECTION``
|
||||
|
||||
Example: ``true`` | ``''`` (empty strings are falsy)
|
||||
@@ -87,23 +119,106 @@ Example build syntax with a single environment variable:
|
||||
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>`__.
|
||||
|
||||
Cloning and Startup
|
||||
===================
|
||||
|
||||
.. code-block::
|
||||
|
||||
|
||||
1. Clone your new repo:
|
||||
|
||||
``git clone https://github.com/openedx/frontend-app-account.git``
|
||||
|
||||
2. Use node v18.x.
|
||||
|
||||
The current version of the micro-frontend build scripts support node 18.
|
||||
Using other major versions of node *may* work, but this is unsupported. For
|
||||
convenience, this repository includes an .nvmrc file to help in setting the
|
||||
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
|
||||
|
||||
3. Install npm dependencies:
|
||||
|
||||
``cd frontend-app-account && npm ci``
|
||||
|
||||
4. Start the dev server:
|
||||
|
||||
``npm start``
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
===========
|
||||
|
||||
None
|
||||
|
||||
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.
|
||||
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/
|
||||
|
||||
Reporting Security Issues
|
||||
=========================
|
||||
|
||||
Please do not report security issues in public. Please email security@openedx.org.
|
||||
|
||||
==============================
|
||||
|
||||
.. |ci-badge| image:: https://github.com/edx/edx-developer-docs/actions/workflows/ci.yml/badge.svg
|
||||
:target: https://github.com/edx/edx-developer-docs/actions/workflows/ci.yml
|
||||
.. |ci-badge| image:: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml/badge.svg
|
||||
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
||||
:alt: Continuous Integration
|
||||
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
|
||||
:target: https://codecov.io/gh/edx/frontend-app-account
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
const { createConfig } = require('@openedx/frontend-build');
|
||||
|
||||
module.exports = createConfig('jest', {
|
||||
setupFilesAfterEnv: [
|
||||
|
||||
33033
package-lock.json
generated
33033
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
86
package.json
86
package.json
@@ -6,50 +6,49 @@
|
||||
"license": "AGPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/edx/frontend-app-account.git"
|
||||
"url": "git+https://github.com/openedx/frontend-app-account.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "fedx-scripts webpack",
|
||||
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
||||
"is-es5": "es-check es5 ./dist/*.js",
|
||||
"i18n_extract": "fedx-scripts formatjs extract",
|
||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||
"start": "fedx-scripts webpack-dev-server --progress",
|
||||
"test": "fedx-scripts jest --coverage --passWithNoTests"
|
||||
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/edx/frontend-app-account/issues"
|
||||
"url": "https://github.com/openedx/frontend-app-account/issues"
|
||||
},
|
||||
"homepage": "https://github.com/edx/frontend-app-account#readme",
|
||||
"homepage": "https://github.com/openedx/frontend-app-account#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"ie 11"
|
||||
"extends @edx/browserslist-config"
|
||||
],
|
||||
"dependencies": {
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||
"@edx/frontend-component-footer": "10.1.6",
|
||||
"@edx/frontend-component-header": "^2.4.3",
|
||||
"@edx/frontend-platform": "1.12.7",
|
||||
"@edx/paragon": "16.1.0",
|
||||
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||
"@edx/frontend-component-footer": "13.0.2",
|
||||
"@edx/frontend-component-header": "5.0.2",
|
||||
"@edx/frontend-platform": "7.1.0",
|
||||
"@edx/openedx-atlas": "^0.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||
"@fortawesome/react-fontawesome": "0.1.15",
|
||||
"@fortawesome/react-fontawesome": "0.2.0",
|
||||
"@openedx/paragon": "22.0.0",
|
||||
"@tensorflow-models/blazeface": "0.0.7",
|
||||
"@tensorflow/tfjs-converter": "1.7.4",
|
||||
"@tensorflow/tfjs-core": "1.7.4",
|
||||
"@tensorflow/tfjs-converter": "3.21.0",
|
||||
"@tensorflow/tfjs-core": "3.21.0",
|
||||
"bowser": "2.11.0",
|
||||
"classnames": "2.3.1",
|
||||
"core-js": "3.18.2",
|
||||
"classnames": "2.5.1",
|
||||
"core-js": "3.35.1",
|
||||
"font-awesome": "4.7.0",
|
||||
"form-urlencoded": "4.0.1",
|
||||
"form-urlencoded": "6.1.4",
|
||||
"formdata-polyfill": "4.0.10",
|
||||
"history": "4.10.1",
|
||||
"jslib-html5-camera-photo": "3.1.8",
|
||||
"jslib-html5-camera-photo": "3.3.4",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.findindex": "4.6.0",
|
||||
"lodash.get": "4.4.2",
|
||||
@@ -58,35 +57,36 @@
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.pickby": "4.6.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"long": "5.2.3",
|
||||
"memoize-one": "5.2.1",
|
||||
"prop-types": "15.7.2",
|
||||
"qs": "6.10.1",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-redux": "7.2.5",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"react-router-hash-link": "1.2.2",
|
||||
"prop-types": "15.8.1",
|
||||
"qs": "6.11.2",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router": "6.22.0",
|
||||
"react-router-dom": "6.22.0",
|
||||
"react-router-hash-link": "2.4.3",
|
||||
"react-scrollspy": "3.4.3",
|
||||
"react-transition-group": "4.4.2",
|
||||
"redux": "4.1.1",
|
||||
"react-transition-group": "4.4.5",
|
||||
"redux": "4.2.1",
|
||||
"redux-devtools-extension": "2.13.9",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-saga": "1.1.3",
|
||||
"redux-thunk": "2.3.0",
|
||||
"regenerator-runtime": "0.13.9",
|
||||
"reselect": "4.0.0",
|
||||
"redux-saga": "1.3.0",
|
||||
"redux-thunk": "2.4.2",
|
||||
"regenerator-runtime": "0.14.1",
|
||||
"reselect": "4.1.8",
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "^9.0.5",
|
||||
"@testing-library/jest-dom": "5.14.1",
|
||||
"@testing-library/react": "12.1.2",
|
||||
"codecov": "3.8.3",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.6",
|
||||
"es-check": "6.0.0",
|
||||
"react-test-renderer": "16.14.0",
|
||||
"@edx/browserslist-config": "1.2.0",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"@openedx/frontend-build": "13.0.27",
|
||||
"@testing-library/jest-dom": "5.17.0",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"react-test-renderer": "17.0.2",
|
||||
"reactifex": "1.1.1",
|
||||
"redux-mock-store": "1.5.4"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,148 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title>Account | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
||||
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||
<script
|
||||
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
||||
></script>
|
||||
<% } %>
|
||||
<title>Account | <%= process.env.SITE_NAME %></title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="<%=htmlWebpackPlugin.options.FAVICON_URL%>"
|
||||
type="image/x-icon"
|
||||
/>
|
||||
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||
<script src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
"pin"
|
||||
],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchPackagePatterns": ["@edx", "@openedx"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"automerge": true
|
||||
}
|
||||
],
|
||||
"timezone": "America/New_York"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { getConfig, history, getQueryParameters } from '@edx/frontend-platform';
|
||||
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, Hyperlink, Icon, Alert,
|
||||
} from '@edx/paragon';
|
||||
import { CheckCircle, Error, WarningFilled } from '@edx/paragon/icons';
|
||||
} from '@openedx/paragon';
|
||||
import { CheckCircle, Error, WarningFilled } from '@openedx/paragon/icons';
|
||||
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
import {
|
||||
@@ -31,35 +31,33 @@ import PageLoading from './PageLoading';
|
||||
import JumpNav from './JumpNav';
|
||||
import DeleteAccount from './delete-account';
|
||||
import EditableField from './EditableField';
|
||||
import EditableSelectField from './EditableSelectField';
|
||||
import ResetPassword from './reset-password';
|
||||
import NameChange from './name-change';
|
||||
import ThirdPartyAuth from './third-party-auth';
|
||||
import BetaLanguageBanner from './BetaLanguageBanner';
|
||||
import EmailField from './EmailField';
|
||||
import OneTimeDismissibleAlert from './OneTimeDismissibleAlert';
|
||||
import DOBModal from './DOBForm';
|
||||
import {
|
||||
YEAR_OF_BIRTH_OPTIONS,
|
||||
EDUCATION_LEVELS,
|
||||
GENDER_OPTIONS,
|
||||
COUNTRY_WITH_STATES,
|
||||
COPPA_COMPLIANCE_YEAR,
|
||||
WORK_EXPERIENCE_OPTIONS,
|
||||
getStatesList,
|
||||
} from './data/constants';
|
||||
import { fetchSiteLanguages } from './site-language';
|
||||
import CoachingToggle from './coaching/CoachingToggle';
|
||||
import DemographicsSection from './demographics/DemographicsSection';
|
||||
import { fetchCourseList } from '../notification-preferences/data/thunks';
|
||||
import { withLocation, withNavigate } from './hoc';
|
||||
|
||||
class AccountSettingsPage extends React.Component {
|
||||
constructor(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;
|
||||
if (duplicateTpaProvider !== undefined) {
|
||||
history.replace(history.location.pathname);
|
||||
}
|
||||
this.state = {
|
||||
duplicateTpaProvider,
|
||||
};
|
||||
@@ -76,8 +74,9 @@ class AccountSettingsPage extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchCourseList();
|
||||
this.props.fetchSettings();
|
||||
this.props.fetchSiteLanguages();
|
||||
this.props.fetchSiteLanguages(this.props.navigate);
|
||||
sendTrackingLogEvent('edx.user.settings.viewed', {
|
||||
page: 'account',
|
||||
visibility: null,
|
||||
@@ -144,30 +143,29 @@ class AccountSettingsPage extends React.Component {
|
||||
value: key,
|
||||
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,
|
||||
})),
|
||||
}));
|
||||
|
||||
sortDates = (a, b) => {
|
||||
const aTimeSinceEpoch = new Date(a).getTime();
|
||||
const bTimeSinceEpoch = new Date(b).getTime();
|
||||
|
||||
return bTimeSinceEpoch - aTimeSinceEpoch;
|
||||
}
|
||||
|
||||
sortVerifiedNameRecords = verifiedNameHistory => {
|
||||
if (Array.isArray(verifiedNameHistory)) {
|
||||
return [...verifiedNameHistory].sort(this.sortDates);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
handleEditableFieldChange = (name, value) => {
|
||||
this.props.updateDraft(name, value);
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = (formId, values) => {
|
||||
this.props.saveSettings(formId, values);
|
||||
}
|
||||
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) => {
|
||||
if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) {
|
||||
@@ -195,7 +193,7 @@ class AccountSettingsPage extends React.Component {
|
||||
} else {
|
||||
this.props.saveSettings(formId, values);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
isEditable(fieldName) {
|
||||
return !this.props.staticFields.includes(fieldName);
|
||||
@@ -212,6 +210,12 @@ class AccountSettingsPage extends React.Component {
|
||||
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 (
|
||||
<div>
|
||||
<Alert variant="danger">
|
||||
@@ -259,27 +263,28 @@ class AccountSettingsPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderFullNameHelpText = (status) => {
|
||||
if (
|
||||
!this.props.verifiedNameHistory
|
||||
|| !this.props.verifiedNameEnabled
|
||||
) {
|
||||
renderFullNameHelpText = (status, proctoredExamId) => {
|
||||
if (!this.props.verifiedNameHistory) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text']);
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'submitted':
|
||||
if (this.props.committedValues.useVerifiedNameForCerts) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted']);
|
||||
}
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted.certificate']);
|
||||
default:
|
||||
if (this.props.committedValues.useVerifiedNameForCerts) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.non.certificate']);
|
||||
}
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.certificate']);
|
||||
let messageString = 'account.settings.field.full.name.help.text';
|
||||
|
||||
if (status === 'submitted') {
|
||||
messageString += '.submitted';
|
||||
if (proctoredExamId) {
|
||||
messageString += '.proctored';
|
||||
}
|
||||
} else {
|
||||
messageString += '.default';
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.props.committedValues.useVerifiedNameForCerts) {
|
||||
messageString += '.certificate';
|
||||
}
|
||||
|
||||
return this.props.intl.formatMessage(messages[messageString]);
|
||||
};
|
||||
|
||||
renderVerifiedNameSuccessMessage = (verifiedName, created) => {
|
||||
const dateValue = new Date(created).valueOf();
|
||||
@@ -294,7 +299,7 @@ class AccountSettingsPage extends React.Component {
|
||||
body={this.props.intl.formatMessage(messages['account.settings.field.name.verified.success.message'])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderVerifiedNameFailureMessage = (verifiedName, created) => {
|
||||
const dateValue = new Date(created).valueOf();
|
||||
@@ -325,7 +330,7 @@ class AccountSettingsPage extends React.Component {
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
renderVerifiedNameSubmittedMessage = (willCertNameChange) => (
|
||||
<Alert
|
||||
@@ -343,7 +348,7 @@ class AccountSettingsPage extends React.Component {
|
||||
}
|
||||
</p>
|
||||
</Alert>
|
||||
)
|
||||
);
|
||||
|
||||
renderVerifiedNameMessage = verifiedNameRecord => {
|
||||
const {
|
||||
@@ -351,6 +356,7 @@ class AccountSettingsPage extends React.Component {
|
||||
status,
|
||||
profile_name: profileName,
|
||||
verified_name: verifiedName,
|
||||
proctored_exam_attempt_id: proctoredExamId,
|
||||
} = verifiedNameRecord;
|
||||
let willCertNameChange = false;
|
||||
|
||||
@@ -369,6 +375,10 @@ class AccountSettingsPage extends React.Component {
|
||||
willCertNameChange = true;
|
||||
}
|
||||
|
||||
if (proctoredExamId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return this.renderVerifiedNameSuccessMessage(verifiedName, created);
|
||||
@@ -379,7 +389,7 @@ class AccountSettingsPage extends React.Component {
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderVerifiedNameIcon = (status) => {
|
||||
switch (status) {
|
||||
@@ -390,24 +400,32 @@ class AccountSettingsPage extends React.Component {
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
renderVerifiedNameHelpText = (status) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
if (this.props.committedValues.useVerifiedNameForCerts) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.certificate']);
|
||||
}
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.verified']);
|
||||
case 'submitted':
|
||||
if (this.props.committedValues.useVerifiedNameForCerts) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.submitted.certificate']);
|
||||
}
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.submitted']);
|
||||
default:
|
||||
return null;
|
||||
renderVerifiedNameHelpText = (status, proctoredExamId) => {
|
||||
let messageStr = 'account.settings.field.name.verified.help.text';
|
||||
|
||||
// add additional string based on status
|
||||
if (status === 'approved') {
|
||||
messageStr += '.verified';
|
||||
} else if (status === 'submitted') {
|
||||
messageStr += '.submitted';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// add additional string if verified name came from a proctored exam attempt
|
||||
if (proctoredExamId) {
|
||||
messageStr += '.proctored';
|
||||
}
|
||||
|
||||
// add additional string based on certificate name use
|
||||
if (this.props.committedValues.useVerifiedNameForCerts) {
|
||||
messageStr += '.certificate';
|
||||
}
|
||||
|
||||
return this.props.intl.formatMessage(messages[messageStr]);
|
||||
};
|
||||
|
||||
renderEmptyStaticFieldMessage() {
|
||||
if (this.isManagedProfile()) {
|
||||
@@ -466,12 +484,14 @@ class AccountSettingsPage extends React.Component {
|
||||
yearOfBirthOptions,
|
||||
educationLevelOptions,
|
||||
genderOptions,
|
||||
workExperienceOptions,
|
||||
} = this.getLocalizedOptions(this.context.locale, this.props.formValues.country);
|
||||
|
||||
// Show State field only if the country is US (could include Canada later)
|
||||
const showState = this.props.formValues.country === COUNTRY_WITH_STATES;
|
||||
const { verifiedName } = this.props;
|
||||
|
||||
const { verifiedName, verifiedNameEnabled } = this.props;
|
||||
const hasWorkExperience = !!this.props.formValues?.extended_profile?.find(field => field.field_name === 'work_experience');
|
||||
|
||||
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
||||
this.props.timeZoneOptions,
|
||||
@@ -480,15 +500,39 @@ class AccountSettingsPage extends React.Component {
|
||||
);
|
||||
|
||||
const hasLinkedTPA = findIndex(this.props.tpaProviders, provider => provider.connected) >= 0;
|
||||
|
||||
// if user is under 13 and does not have cookie set
|
||||
const shouldUpdateDOB = (
|
||||
getConfig().ENABLE_COPPA_COMPLIANCE
|
||||
&& getConfig().ENABLE_DOB_UPDATE
|
||||
&& this.props.formValues.year_of_birth.toString() >= COPPA_COMPLIANCE_YEAR.toString()
|
||||
&& !localStorage.getItem('submittedDOB')
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="account-section" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
|
||||
{ shouldUpdateDOB
|
||||
&& (
|
||||
<DOBModal
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
)}
|
||||
<div className="account-section pt-3 mb-5" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
|
||||
{
|
||||
verifiedNameEnabled && this.props.mostRecentVerifiedName
|
||||
this.props.mostRecentVerifiedName
|
||||
&& this.renderVerifiedNameMessage(this.props.mostRecentVerifiedName)
|
||||
}
|
||||
{localStorage.getItem('submittedDOB')
|
||||
&& (
|
||||
<OneTimeDismissibleAlert
|
||||
id="updated-dob"
|
||||
variant="success"
|
||||
icon={CheckCircle}
|
||||
header={this.props.intl.formatMessage(messages['account.settings.field.dob.form.success'])}
|
||||
body=""
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 className="section-heading">
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{this.props.intl.formatMessage(messages['account.settings.section.account.information'])}
|
||||
</h2>
|
||||
<p>{this.props.intl.formatMessage(messages['account.settings.section.account.information.description'])}</p>
|
||||
@@ -512,8 +556,7 @@ class AccountSettingsPage extends React.Component {
|
||||
name="name"
|
||||
type="text"
|
||||
value={
|
||||
verifiedNameEnabled
|
||||
&& verifiedName?.status === 'submitted'
|
||||
verifiedName?.status === 'submitted'
|
||||
&& this.props.formValues.pending_name_change
|
||||
? this.props.formValues.pending_name_change
|
||||
: this.props.formValues.name
|
||||
@@ -525,22 +568,22 @@ class AccountSettingsPage extends React.Component {
|
||||
: this.renderEmptyStaticFieldMessage()
|
||||
}
|
||||
helpText={
|
||||
verifiedNameEnabled && verifiedName
|
||||
? this.renderFullNameHelpText(verifiedName.status)
|
||||
verifiedName
|
||||
? this.renderFullNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)
|
||||
: this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text'])
|
||||
}
|
||||
isEditable={
|
||||
verifiedNameEnabled && verifiedName
|
||||
verifiedName
|
||||
? this.isEditable('verifiedName') && this.isEditable('name')
|
||||
: this.isEditable('name')
|
||||
}
|
||||
isGrayedOut={
|
||||
verifiedNameEnabled && verifiedName && !this.isEditable('verifiedName')
|
||||
verifiedName && !this.isEditable('verifiedName')
|
||||
}
|
||||
onChange={this.handleEditableFieldChange}
|
||||
onSubmit={this.handleSubmitProfileName}
|
||||
/>
|
||||
{verifiedNameEnabled && verifiedName
|
||||
{verifiedName
|
||||
&& (
|
||||
<EditableField
|
||||
name="verified_name"
|
||||
@@ -556,7 +599,7 @@ class AccountSettingsPage extends React.Component {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
helpText={this.renderVerifiedNameHelpText(verifiedName.status)}
|
||||
helpText={this.renderVerifiedNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)}
|
||||
isEditable={this.isEditable('verifiedName')}
|
||||
isGrayedOut={!this.isEditable('verifiedName')}
|
||||
onChange={this.handleEditableFieldChange}
|
||||
@@ -583,16 +626,19 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
{this.renderSecondaryEmailField(editableFieldProps)}
|
||||
<ResetPassword email={this.props.formValues.email} />
|
||||
<EditableField
|
||||
name="year_of_birth"
|
||||
type="select"
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.dob'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.dob.empty'])}
|
||||
value={this.props.formValues.year_of_birth}
|
||||
options={yearOfBirthOptions}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
{(!getConfig().ENABLE_COPPA_COMPLIANCE)
|
||||
&& (
|
||||
<EditableSelectField
|
||||
name="year_of_birth"
|
||||
type="select"
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.dob'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.dob.empty'])}
|
||||
value={this.props.formValues.year_of_birth}
|
||||
options={yearOfBirthOptions}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
)}
|
||||
<EditableSelectField
|
||||
name="country"
|
||||
type="select"
|
||||
value={this.props.formValues.country}
|
||||
@@ -608,7 +654,7 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
{showState
|
||||
&& (
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="state"
|
||||
type="select"
|
||||
value={this.props.formValues.state}
|
||||
@@ -625,21 +671,23 @@ class AccountSettingsPage extends React.Component {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="profile-information" ref={this.navLinkRefs['#profile-information']}>
|
||||
<h2 className="section-heading">
|
||||
<div className="account-section pt-3 mb-5" id="profile-information" ref={this.navLinkRefs['#profile-information']}>
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{this.props.intl.formatMessage(messages['account.settings.section.profile.information'])}
|
||||
</h2>
|
||||
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="level_of_education"
|
||||
type="select"
|
||||
value={this.props.formValues.level_of_education}
|
||||
options={educationLevelOptions}
|
||||
options={getConfig().ENABLE_COPPA_COMPLIANCE
|
||||
? educationLevelOptions.filter(option => option.value !== 'el')
|
||||
: educationLevelOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.education'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.education.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="gender"
|
||||
type="select"
|
||||
value={this.props.formValues.gender}
|
||||
@@ -648,7 +696,19 @@ class AccountSettingsPage extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.gender.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
{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
|
||||
name="language_proficiencies"
|
||||
type="select"
|
||||
value={this.props.formValues.language_proficiencies}
|
||||
@@ -657,19 +717,10 @@ class AccountSettingsPage extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
{getConfig().COACHING_ENABLED
|
||||
&& this.props.formValues.coaching.eligible_for_coaching
|
||||
&& (
|
||||
<CoachingToggle
|
||||
name="coaching"
|
||||
phone_number={this.props.formValues.phone_number}
|
||||
coaching={this.props.formValues.coaching}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
|
||||
<div className="account-section" id="social-media">
|
||||
<h2 className="section-heading">
|
||||
<div className="account-section pt-3 mb-5" id="social-media">
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||
</h2>
|
||||
<p>
|
||||
@@ -705,13 +756,13 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
||||
<h2 className="section-heading">
|
||||
<div className="account-section pt-3 mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
||||
</h2>
|
||||
|
||||
<BetaLanguageBanner />
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="siteLanguage"
|
||||
type="select"
|
||||
options={this.props.siteLanguageOptions}
|
||||
@@ -720,7 +771,7 @@ class AccountSettingsPage extends React.Component {
|
||||
helpText={this.props.intl.formatMessage(messages['account.settings.field.site.language.help.text'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="time_zone"
|
||||
type="select"
|
||||
value={this.props.formValues.time_zone}
|
||||
@@ -736,8 +787,8 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="linked-accounts" ref={this.navLinkRefs['#linked-accounts']}>
|
||||
<h2 className="section-heading">{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts'])}</h2>
|
||||
<div className="account-section pt-3 mb-5" id="linked-accounts" ref={this.navLinkRefs['#linked-accounts']}>
|
||||
<h2 className="section-heading h4 mb-3">{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts'])}</h2>
|
||||
<p>
|
||||
{this.props.intl.formatMessage(
|
||||
messages['account.settings.section.linked.accounts.description'],
|
||||
@@ -747,12 +798,15 @@ class AccountSettingsPage extends React.Component {
|
||||
<ThirdPartyAuth />
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
||||
<DeleteAccount
|
||||
isVerifiedAccount={this.props.isActive}
|
||||
hasLinkedTPA={hasLinkedTPA}
|
||||
/>
|
||||
</div>
|
||||
{getConfig().ENABLE_ACCOUNT_DELETION
|
||||
&& (
|
||||
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
||||
<DeleteAccount
|
||||
isVerifiedAccount={this.props.isActive}
|
||||
hasLinkedTPA={hasLinkedTPA}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</>
|
||||
);
|
||||
@@ -789,12 +843,12 @@ class AccountSettingsPage extends React.Component {
|
||||
</h1>
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<div className="col-md-2">
|
||||
<JumpNav
|
||||
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-9">
|
||||
<div className="col-md-10">
|
||||
{loading ? this.renderLoading() : null}
|
||||
{loaded ? this.renderContent() : null}
|
||||
{loadingError ? this.renderError() : null}
|
||||
@@ -825,6 +879,7 @@ AccountSettingsPage.propTypes = {
|
||||
country: PropTypes.string,
|
||||
level_of_education: PropTypes.string,
|
||||
gender: PropTypes.string,
|
||||
extended_profile: PropTypes.string,
|
||||
language_proficiencies: PropTypes.string,
|
||||
pending_name_change: PropTypes.string,
|
||||
phone_number: PropTypes.string,
|
||||
@@ -832,11 +887,6 @@ AccountSettingsPage.propTypes = {
|
||||
social_link_facebook: PropTypes.string,
|
||||
social_link_twitter: 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,
|
||||
shouldDisplayDemographicsSection: PropTypes.bool,
|
||||
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
||||
@@ -878,27 +928,32 @@ AccountSettingsPage.propTypes = {
|
||||
saveSettings: PropTypes.func.isRequired,
|
||||
fetchSettings: PropTypes.func.isRequired,
|
||||
beginNameChange: PropTypes.func.isRequired,
|
||||
fetchCourseList: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||
connected: PropTypes.bool,
|
||||
})),
|
||||
nameChangeModal: PropTypes.shape({
|
||||
formId: PropTypes.string,
|
||||
}),
|
||||
verifiedNameEnabled: PropTypes.bool,
|
||||
verifiedName: PropTypes.shape({
|
||||
verified_name: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
proctored_exam_attempt_id: PropTypes.number,
|
||||
}),
|
||||
mostRecentVerifiedName: PropTypes.shape({
|
||||
verified_name: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
proctored_exam_attempt_id: PropTypes.number,
|
||||
}),
|
||||
verifiedNameHistory: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
verified_name: PropTypes.string,
|
||||
status: PropTypes.string,
|
||||
proctored_exam_attempt_id: PropTypes.number,
|
||||
}),
|
||||
),
|
||||
navigate: PropTypes.func.isRequired,
|
||||
location: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AccountSettingsPage.defaultProps = {
|
||||
@@ -921,17 +976,17 @@ AccountSettingsPage.defaultProps = {
|
||||
isActive: true,
|
||||
secondary_email_enabled: false,
|
||||
nameChangeModal: {},
|
||||
verifiedNameEnabled: false,
|
||||
verifiedName: null,
|
||||
mostRecentVerifiedName: {},
|
||||
verifiedNameHistory: [],
|
||||
};
|
||||
|
||||
export default connect(accountSettingsPageSelector, {
|
||||
export default withLocation(withNavigate(connect(accountSettingsPageSelector, {
|
||||
fetchCourseList,
|
||||
fetchSettings,
|
||||
saveSettings,
|
||||
saveMultipleSettings,
|
||||
updateDraft,
|
||||
fetchSiteLanguages,
|
||||
beginNameChange,
|
||||
})(injectIntl(AccountSettingsPage));
|
||||
})(injectIntl(AccountSettingsPage))));
|
||||
|
||||
@@ -91,13 +91,13 @@ const messages = defineMessages({
|
||||
defaultMessage: 'The name that is used for ID verification and that appears on your certificates.',
|
||||
description: 'Help text for the account settings name field.',
|
||||
},
|
||||
'account.settings.field.full.name.help.text.non.certificate': {
|
||||
id: 'account.settings.field.full.name.help.text.non.certificate',
|
||||
'account.settings.field.full.name.help.text.default': {
|
||||
id: 'account.settings.field.full.name.help.text.default',
|
||||
defaultMessage: 'The name that appears on your public profile.',
|
||||
description: 'Help text for the account settings name field.',
|
||||
},
|
||||
'account.settings.field.full.name.help.text.certificate': {
|
||||
id: 'account.settings.field.full.name.help.text.certificate',
|
||||
'account.settings.field.full.name.help.text.default.certificate': {
|
||||
id: 'account.settings.field.full.name.help.text.default.certificate',
|
||||
defaultMessage: 'This name is selected to appear on your certificates and public-facing records.',
|
||||
description: 'Help text for the account settings name field.',
|
||||
},
|
||||
@@ -108,27 +108,47 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.verified': {
|
||||
id: 'account.settings.field.name.verified.help.text.verified',
|
||||
defaultMessage: 'This name has been verified by government ID.',
|
||||
defaultMessage: 'This name has been verified by photo ID.',
|
||||
description: 'Help text for the account settings verified name field when the name is verified.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.certificate': {
|
||||
id: 'account.settings.field.name.verified.help.text.certificate',
|
||||
defaultMessage: 'This name has been verified by government ID and selected to appear on your certificates and public-facing records.',
|
||||
'account.settings.field.name.verified.help.text.verified.proctored': {
|
||||
id: 'account.settings.field.name.verified.help.text.verified.proctored',
|
||||
defaultMessage: 'This name has been verified by proctoring.',
|
||||
description: 'Help text for the account settings verified name field when the name is verified through proctoring.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.verified.certificate': {
|
||||
id: 'account.settings.field.name.verified.help.text.verified.certificate',
|
||||
defaultMessage: 'This name has been verified by photo ID, and is selected to appear on your certificates and public-facing records.',
|
||||
description: 'Help text for the account settings verified name field when the name is selected for certificates.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.verified.proctored.certificate': {
|
||||
id: 'account.settings.field.name.verified.help.text.verified.proctored.certificate',
|
||||
defaultMessage: 'This name has been verified by proctoring, and is selected to appear on your certificates and public-facing records.',
|
||||
description: 'Help text for the account settings verified name field when the name is selected for certificates, and the name is verified through proctoring.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.submitted': {
|
||||
id: 'account.settings.field.name.verified.help.text.submitted',
|
||||
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. 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.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.submitted.proctored': {
|
||||
id: 'account.settings.field.name.verified.help.text.submitted.proctored',
|
||||
defaultMessage: 'Your proctored exam has been submitted. Verified name cannot be changed at this time. Please check back in 2-5 days.',
|
||||
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.submitted.certificate': {
|
||||
id: 'account.settings.field.name.verified.help.text.submitted.certificate',
|
||||
defaultMessage: 'When identity verification is successful, this name will appear on your certificates 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 and will appear on certificates.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.submitted.proctored.certificate': {
|
||||
id: 'account.settings.field.name.verified.help.text.submitted.proctored.certificate',
|
||||
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.',
|
||||
},
|
||||
'account.settings.field.name.verified.verification.alert': {
|
||||
id: 'account.settings.field.name.verified.verification.help',
|
||||
defaultMessage: 'Enter your name as it appears on your government-issued ID.',
|
||||
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': {
|
||||
@@ -136,11 +156,21 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
||||
description: 'Help text for the account settings full name field when a verified name has been submitted.',
|
||||
},
|
||||
'account.settings.field.full.name.help.text.submitted.proctored': {
|
||||
id: 'account.settings.field.full.name.help.text.submitted.proctored',
|
||||
defaultMessage: 'Your proctored exam has been submitted. Full name cannot be changed at this time. Please check back in 2-5 days.',
|
||||
description: 'Help text for the account settings full name field when a verified name has been submitted through proctoring.',
|
||||
},
|
||||
'account.settings.field.full.name.help.text.submitted.certificate': {
|
||||
id: 'account.settings.field.full.name.help.text.submitted.certificate',
|
||||
defaultMessage: 'When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.',
|
||||
description: 'Help text for the account settings full name field when a full name has been submitted and will appear on certificates.',
|
||||
},
|
||||
'account.settings.field.full.name.help.text.submitted.proctored.certificate': {
|
||||
id: 'account.settings.field.full.name.help.text.submitted.proctored.certificate',
|
||||
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.',
|
||||
description: 'Help text for the account settings full name field when a full name has been submitted and will appear on certificates.',
|
||||
},
|
||||
'account.settings.field.name.verified.success.message': {
|
||||
id: 'account.settings.field.name.verified.success.message',
|
||||
defaultMessage: 'Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.',
|
||||
@@ -236,6 +266,56 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Select a year of birth',
|
||||
description: 'Option for empty value on account settings year of birth field.',
|
||||
},
|
||||
'account.settings.field.dob.month': {
|
||||
id: 'account.settings.field.dob.month',
|
||||
defaultMessage: 'Month',
|
||||
description: 'Label for account settings month of birth field.',
|
||||
},
|
||||
'account.settings.field.dob.year': {
|
||||
id: 'account.settings.field.dob.year',
|
||||
defaultMessage: 'Year',
|
||||
description: 'Label for account settings year of birth field.',
|
||||
},
|
||||
'account.settings.field.dob.month.default': {
|
||||
id: 'account.settings.field.month.year.default',
|
||||
defaultMessage: 'Select month',
|
||||
description: 'Default label for account settings month of birth field.',
|
||||
},
|
||||
'account.settings.field.dob.year.default': {
|
||||
id: 'account.settings.field.dob.year.default',
|
||||
defaultMessage: 'Select year',
|
||||
description: 'Default label for account settings year of birth field.',
|
||||
},
|
||||
'account.settings.field.dob.form.button': {
|
||||
id: 'account.settings.field.dob.form.button',
|
||||
defaultMessage: 'Please confirm your date of birth',
|
||||
description: 'Message to prompt user to enter dob',
|
||||
},
|
||||
'account.settings.field.dob.form.title': {
|
||||
id: 'account.settings.field.dob.form.title',
|
||||
defaultMessage: 'Enter your birth month and year',
|
||||
description: 'Title of DOB form',
|
||||
},
|
||||
'account.settings.field.dob.form.help.text': {
|
||||
id: 'account.settings.field.dob.form.help.text',
|
||||
defaultMessage: 'We ask for birth month and year information to help us comply with our legal obligations.',
|
||||
description: 'Help text for DOB form',
|
||||
},
|
||||
'account.settings.field.dob.form.success': {
|
||||
id: 'account.settings.field.dob.form.success',
|
||||
defaultMessage: 'Thank you for entering your information.',
|
||||
description: 'Title of banner when date of birth is successfully entered',
|
||||
},
|
||||
'account.settings.field.month_of_birth.options.empty': {
|
||||
id: 'account.settings.field.month_of_birth.options.empty',
|
||||
defaultMessage: 'Select a month of birth',
|
||||
description: 'Option for empty value on account settings month of birth field.',
|
||||
},
|
||||
'account.settingsfield.dob.error.general': {
|
||||
id: 'account.settingsfield.dob.error.general',
|
||||
defaultMessage: 'A technical error occurred. Please try again.',
|
||||
description: 'Generic error message.',
|
||||
},
|
||||
'account.settings.field.country': {
|
||||
id: 'account.settings.field.country',
|
||||
defaultMessage: 'Country',
|
||||
@@ -331,8 +411,8 @@ const messages = defineMessages({
|
||||
defaultMessage: 'No formal education',
|
||||
description: 'Selected by the user to describe their education.',
|
||||
},
|
||||
'account.settings.field.education.levels.o': {
|
||||
id: 'account.settings.field.education.levels.o',
|
||||
'account.settings.field.education.levels.other': {
|
||||
id: 'account.settings.field.education.levels.other',
|
||||
defaultMessage: 'Other education',
|
||||
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
||||
},
|
||||
@@ -485,6 +565,26 @@ const messages = defineMessages({
|
||||
defaultMessage: 'No value set.',
|
||||
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;
|
||||
|
||||
@@ -2,18 +2,16 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
function Alert(props) {
|
||||
return (
|
||||
<div className={classNames('alert d-flex align-items-start', props.className)}>
|
||||
<div>
|
||||
{props.icon}
|
||||
</div>
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
const Alert = (props) => (
|
||||
<div className={classNames('alert d-flex align-items-start', props.className)}>
|
||||
<div>
|
||||
{props.icon}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Alert.propTypes = {
|
||||
className: PropTypes.string,
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
import { Button, Hyperlink } from '@openedx/paragon';
|
||||
|
||||
import { betaLanguageBannerSelector } from './data/selectors';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
@@ -49,6 +49,9 @@ class BetaLanguageBanner extends React.Component {
|
||||
|
||||
render() {
|
||||
const savedLanguage = this.getSiteLanguageEntry(this.context.locale);
|
||||
if (!savedLanguage) {
|
||||
return null;
|
||||
}
|
||||
const isSavedLanguageReleased = savedLanguage.released === true;
|
||||
const noPreviousLanguageSet = this.props.siteLanguage.previousValue === null;
|
||||
if (isSavedLanguageReleased || noPreviousLanguageSet) {
|
||||
|
||||
162
src/account-settings/DOBForm.jsx
Normal file
162
src/account-settings/DOBForm.jsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
||||
} from '@openedx/paragon';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
import { saveSettingsReset } from './data/actions';
|
||||
|
||||
const DOBModal = (props) => {
|
||||
const {
|
||||
saveState,
|
||||
error,
|
||||
onSubmit,
|
||||
intl,
|
||||
} = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [isOpen, open, close, toggle] = useToggle(true, {});
|
||||
const [monthValue, setMonthValue] = useState('');
|
||||
const [yearValue, setYearValue] = useState('');
|
||||
|
||||
const handleChange = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.target.name === 'month') {
|
||||
setMonthValue(e.target.value);
|
||||
} else if (e.target.name === 'year') {
|
||||
setYearValue(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const data = monthValue !== '' && yearValue !== '' ? [{ field_name: 'DOB', field_value: `${yearValue}-${monthValue}` }] : [];
|
||||
onSubmit('extended_profile', data);
|
||||
};
|
||||
|
||||
const handleComplete = useCallback(() => {
|
||||
localStorage.setItem('submittedDOB', 'true');
|
||||
close();
|
||||
dispatch(saveSettingsReset());
|
||||
}, [dispatch, close]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
close();
|
||||
dispatch(saveSettingsReset());
|
||||
}, [dispatch, close]);
|
||||
|
||||
function renderErrors() {
|
||||
if (saveState === 'error' || error) {
|
||||
return (
|
||||
<Form.Control.Feedback type="invalid" key="general-error">
|
||||
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
|
||||
</Form.Control.Feedback>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (saveState === 'complete' && isOpen) {
|
||||
handleComplete();
|
||||
}
|
||||
}, [handleComplete, saveState, isOpen, monthValue, yearValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="primary" onClick={open}>
|
||||
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
|
||||
</Button>
|
||||
<ModalDialog
|
||||
title={intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
||||
isOpen={isOpen}
|
||||
onClose={handleClose}
|
||||
hasCloseButton={false}
|
||||
variant="default"
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
|
||||
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
|
||||
<p>{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
|
||||
<Form.Group>
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages['account.settings.field.dob.month'])}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="month"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.month.default'])}</option>
|
||||
{[...Array(12).keys()].map(month => (
|
||||
<option key={month + 1} value={month + 1}>{month + 1}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages['account.settings.field.dob.year'])}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
name="year"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.year.default'])}</option>
|
||||
{YEAR_OF_BIRTH_OPTIONS.map(year => (
|
||||
<option key={year.value} value={year.value}>{year.label}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
{renderErrors()}
|
||||
</ModalDialog.Body>
|
||||
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
Cancel
|
||||
</ModalDialog.CloseButton>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
state={!(monthValue && yearValue) ? 'unedited' : saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||
}}
|
||||
disabledStates={['unedited']}
|
||||
/>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
|
||||
</form>
|
||||
</ModalDialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
DOBModal.propTypes = {
|
||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||
error: PropTypes.string,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
DOBModal.defaultProps = {
|
||||
saveState: undefined,
|
||||
error: undefined,
|
||||
};
|
||||
|
||||
export default connect(editableFieldSelector)(injectIntl(DOBModal));
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, Input, StatefulButton, ValidationFormGroup,
|
||||
} from '@edx/paragon';
|
||||
Button, Form, StatefulButton,
|
||||
} from '@openedx/paragon';
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
import CertificatePreference from './certificate-preference/CertificatePreference';
|
||||
|
||||
function EditableField(props) {
|
||||
const EditableField = (props) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
@@ -26,7 +27,6 @@ function EditableField(props) {
|
||||
type,
|
||||
value,
|
||||
userSuppliedValue,
|
||||
options,
|
||||
saveState,
|
||||
error,
|
||||
confirmationMessageDefinition,
|
||||
@@ -74,15 +74,6 @@ function EditableField(props) {
|
||||
}
|
||||
let finalValue = rawValue;
|
||||
|
||||
if (options) {
|
||||
// Use == instead of === to prevent issues when HTML casts numbers as strings
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const selectedOption = options.find(option => option.value == rawValue);
|
||||
if (selectedOption) {
|
||||
finalValue = selectedOption.label;
|
||||
}
|
||||
}
|
||||
|
||||
if (userSuppliedValue) {
|
||||
finalValue += `: ${userSuppliedValue}`;
|
||||
}
|
||||
@@ -106,25 +97,24 @@ function EditableField(props) {
|
||||
editing: (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={id}
|
||||
invalid={error != null}
|
||||
invalidMessage={error}
|
||||
helpText={helpText}
|
||||
<Form.Group
|
||||
controlId={id}
|
||||
isInvalid={error != null}
|
||||
>
|
||||
<label className="h6 d-block" htmlFor={id}>{label}</label>
|
||||
<Input
|
||||
<Form.Label size="sm" className="h6 d-block" htmlFor={id}>{label}</Form.Label>
|
||||
<Form.Control
|
||||
data-hj-suppress
|
||||
name={name}
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
{...others}
|
||||
/>
|
||||
<>{others.children}</>
|
||||
</ValidationFormGroup>
|
||||
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
||||
{error != null && <Form.Control.Feedback hasIcon={false}>{error}</Form.Control.Feedback>}
|
||||
{others.children}
|
||||
</Form.Group>
|
||||
<p>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
@@ -166,14 +156,14 @@ function EditableField(props) {
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
|
||||
<p data-hj-suppress className={classNames('text-truncate', { 'grayed-out': isGrayedOut })}>{renderValue(value)}</p>
|
||||
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
EditableField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
@@ -182,10 +172,6 @@ EditableField.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
userSuppliedValue: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
})),
|
||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||
error: PropTypes.string,
|
||||
confirmationMessageDefinition: PropTypes.shape({
|
||||
@@ -207,7 +193,6 @@ EditableField.propTypes = {
|
||||
|
||||
EditableField.defaultProps = {
|
||||
value: undefined,
|
||||
options: undefined,
|
||||
saveState: undefined,
|
||||
label: undefined,
|
||||
emptyLabel: undefined,
|
||||
|
||||
251
src/account-settings/EditableSelectField.jsx
Normal file
251
src/account-settings/EditableSelectField.jsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, Form, StatefulButton,
|
||||
} from '@openedx/paragon';
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import SwitchContent from './SwitchContent';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
|
||||
import {
|
||||
openForm,
|
||||
closeForm,
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
import CertificatePreference from './certificate-preference/CertificatePreference';
|
||||
|
||||
const EditableSelectField = (props) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
emptyLabel,
|
||||
type,
|
||||
value,
|
||||
userSuppliedValue,
|
||||
options,
|
||||
saveState,
|
||||
error,
|
||||
confirmationMessageDefinition,
|
||||
confirmationValue,
|
||||
helpText,
|
||||
onEdit,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onChange,
|
||||
isEditing,
|
||||
isEditable,
|
||||
isGrayedOut,
|
||||
intl,
|
||||
...others
|
||||
} = props;
|
||||
const id = `field-${name}`;
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
onSubmit(name, new FormData(e.target).get(name));
|
||||
};
|
||||
|
||||
const handleChange = (e) => {
|
||||
onChange(name, e.target.value);
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
onEdit(name);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel(name);
|
||||
};
|
||||
|
||||
const renderEmptyLabel = () => {
|
||||
if (isEditable) {
|
||||
return <Button variant="link" onClick={handleEdit} className="p-0">{emptyLabel}</Button>;
|
||||
}
|
||||
return <span className="text-muted">{emptyLabel}</span>;
|
||||
};
|
||||
|
||||
const renderValue = (rawValue) => {
|
||||
if (!rawValue) {
|
||||
return renderEmptyLabel();
|
||||
}
|
||||
let finalValue = rawValue;
|
||||
|
||||
if (options) {
|
||||
// Use == instead of === to prevent issues when HTML casts numbers as strings
|
||||
// eslint-disable-next-line eqeqeq
|
||||
const selectedOption = options.find(option => option.value == rawValue);
|
||||
if (selectedOption) {
|
||||
finalValue = selectedOption.label;
|
||||
}
|
||||
}
|
||||
|
||||
if (userSuppliedValue) {
|
||||
finalValue += `: ${userSuppliedValue}`;
|
||||
}
|
||||
|
||||
return finalValue;
|
||||
};
|
||||
|
||||
const renderConfirmationMessage = () => {
|
||||
if (!confirmationMessageDefinition || !confirmationValue) {
|
||||
return null;
|
||||
}
|
||||
return intl.formatMessage(confirmationMessageDefinition, {
|
||||
value: confirmationValue,
|
||||
});
|
||||
};
|
||||
const selectOptions = options.map((option) => {
|
||||
if (option.group) {
|
||||
// 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}`}
|
||||
>
|
||||
{subOption.label}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<option value={option.value} key={`${option.value}-${option.label}`}>
|
||||
{option.label}
|
||||
</option>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<SwitchContent
|
||||
expression={isEditing ? 'editing' : 'default'}
|
||||
cases={{
|
||||
editing: (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Form.Group
|
||||
controlId={id}
|
||||
isInvalid={error != null}
|
||||
>
|
||||
<Form.Label size="sm" className="h6 d-block" htmlFor={id}>{label}</Form.Label>
|
||||
<Form.Control
|
||||
data-hj-suppress
|
||||
name={name}
|
||||
id={id}
|
||||
type={type}
|
||||
as={type}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
{...others}
|
||||
>
|
||||
{options.length > 0 && selectOptions}
|
||||
</Form.Control>
|
||||
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
||||
{error != null && <Form.Control.Feedback>{error}</Form.Control.Feedback>}
|
||||
{others.children}
|
||||
</Form.Group>
|
||||
<p>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="mr-2"
|
||||
state={saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// Swallow clicks if the state is pending.
|
||||
// We do this instead of disabling the button to prevent
|
||||
// it from losing focus (disabled elements cannot have focus).
|
||||
// Disabling it would causes upstream issues in focus management.
|
||||
// Swallowing the onSubmit event on the form would be better, but
|
||||
// we would have to add that logic for every field given our
|
||||
// current structure of the application.
|
||||
if (saveState === 'pending') { e.preventDefault(); }
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
||||
</Button>
|
||||
</p>
|
||||
</form>
|
||||
{['name', 'verified_name'].includes(name) && <CertificatePreference fieldName={name} />}
|
||||
</>
|
||||
),
|
||||
default: (
|
||||
<div className="form-group">
|
||||
<div className="d-flex align-items-start">
|
||||
<h6 aria-level="3">{label}</h6>
|
||||
{isEditable ? (
|
||||
<Button variant="link" onClick={handleEdit} className="ml-3">
|
||||
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />{intl.formatMessage(messages['account.settings.editable.field.action.edit'])}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
|
||||
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
EditableSelectField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]),
|
||||
emptyLabel: PropTypes.node,
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
userSuppliedValue: PropTypes.string,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
})),
|
||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||
error: PropTypes.string,
|
||||
confirmationMessageDefinition: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
defaultMessage: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
}),
|
||||
confirmationValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
helpText: PropTypes.node,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
isEditable: PropTypes.bool,
|
||||
isGrayedOut: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
EditableSelectField.defaultProps = {
|
||||
value: undefined,
|
||||
options: [],
|
||||
saveState: undefined,
|
||||
label: undefined,
|
||||
emptyLabel: undefined,
|
||||
error: undefined,
|
||||
confirmationMessageDefinition: undefined,
|
||||
confirmationValue: undefined,
|
||||
helpText: undefined,
|
||||
isEditing: false,
|
||||
isEditable: true,
|
||||
isGrayedOut: false,
|
||||
userSuppliedValue: undefined,
|
||||
};
|
||||
|
||||
export default connect(editableFieldSelector, {
|
||||
onEdit: openForm,
|
||||
onCancel: closeForm,
|
||||
})(injectIntl(EditableSelectField));
|
||||
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Button, StatefulButton, Input, ValidationFormGroup,
|
||||
} from '@edx/paragon';
|
||||
Button, StatefulButton, Form,
|
||||
} from '@openedx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
|
||||
function EmailField(props) {
|
||||
const EmailField = (props) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
@@ -106,14 +106,12 @@ function EmailField(props) {
|
||||
cases={{
|
||||
editing: (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={id}
|
||||
invalid={error != null}
|
||||
invalidMessage={error}
|
||||
helpText={helpText}
|
||||
<Form.Group
|
||||
controlId={id}
|
||||
isInvalid={error != null}
|
||||
>
|
||||
<label className="h6 d-block" htmlFor={id}>{label}</label>
|
||||
<Input
|
||||
<Form.Label className="h6 d-block" htmlFor={id}>{label}</Form.Label>
|
||||
<Form.Control
|
||||
data-hj-suppress
|
||||
name={name}
|
||||
id={id}
|
||||
@@ -121,7 +119,9 @@ function EmailField(props) {
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
||||
{error != null && <Form.Control.Feedback hasIcon={false}>{error}</Form.Control.Feedback>}
|
||||
</Form.Group>
|
||||
<p>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
@@ -169,7 +169,7 @@ function EmailField(props) {
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
EmailField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { breakpoints, useWindowSize, Icon } from '@openedx/paragon';
|
||||
import { OpenInNew } from '@openedx/paragon/icons';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { NavHashLink } from 'react-router-hash-link';
|
||||
import Scrollspy from 'react-scrollspy';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
import { selectShowPreferences } from '../notification-preferences/data/selectors';
|
||||
|
||||
const JumpNav = ({
|
||||
intl,
|
||||
displayDemographicsLink,
|
||||
}) => {
|
||||
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
||||
const showPreferences = useSelector(selectShowPreferences());
|
||||
|
||||
function JumpNav({ intl, displayDemographicsLink }) {
|
||||
return (
|
||||
<div className="jump-nav">
|
||||
<div className={classNames('jump-nav px-2.25', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
||||
<Scrollspy
|
||||
items={[
|
||||
'basic-information',
|
||||
@@ -56,15 +67,33 @@ function JumpNav({ intl, displayDemographicsLink }) {
|
||||
{intl.formatMessage(messages['account.settings.section.linked.accounts'])}
|
||||
</NavHashLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavHashLink to="#delete-account">
|
||||
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
||||
</NavHashLink>
|
||||
</li>
|
||||
{getConfig().ENABLE_ACCOUNT_DELETION
|
||||
&& (
|
||||
<li>
|
||||
<NavHashLink to="#delete-account">
|
||||
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
||||
</NavHashLink>
|
||||
</li>
|
||||
)}
|
||||
</Scrollspy>
|
||||
{showPreferences && (
|
||||
<>
|
||||
<hr />
|
||||
<Scrollspy
|
||||
className="list-unstyled"
|
||||
>
|
||||
<li>
|
||||
<Link to="/notifications" target="_blank" rel="noopener noreferrer">
|
||||
<span>{intl.formatMessage(messages['notification.preferences.notifications.label'])}</span>
|
||||
<Icon className="d-inline-block align-bottom ml-1" src={OpenInNew} />
|
||||
</Link>
|
||||
</li>
|
||||
</Scrollspy>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
JumpNav.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||
<FormattedMessage
|
||||
id="error.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const NotFoundPage = () => (
|
||||
<div
|
||||
className="container-fluid d-flex py-5 justify-content-center align-items-start text-center"
|
||||
data-testid="not-found-page"
|
||||
>
|
||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||
<FormattedMessage
|
||||
id="error.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="Error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default NotFoundPage;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { Alert } from '@openedx/paragon';
|
||||
|
||||
export default function OneTimeDismissibleAlert(props) {
|
||||
const OneTimeDismissibleAlert = (props) => {
|
||||
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
||||
|
||||
const onClose = () => {
|
||||
@@ -25,7 +25,7 @@ export default function OneTimeDismissibleAlert(props) {
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
OneTimeDismissibleAlert.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
@@ -41,3 +41,5 @@ OneTimeDismissibleAlert.defaultProps = {
|
||||
header: undefined,
|
||||
body: undefined,
|
||||
};
|
||||
|
||||
export default OneTimeDismissibleAlert;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TransitionReplace } from '@edx/paragon';
|
||||
import { TransitionReplace } from '@openedx/paragon';
|
||||
|
||||
const onChildExit = (htmlNode) => {
|
||||
// If the leaving child has focus, take control and redirect it
|
||||
@@ -22,7 +22,7 @@ const onChildExit = (htmlNode) => {
|
||||
}
|
||||
};
|
||||
|
||||
function SwitchContent({ expression, cases, className }) {
|
||||
const SwitchContent = ({ expression, cases, className }) => {
|
||||
const getContent = (caseKey) => {
|
||||
if (cases[caseKey]) {
|
||||
if (typeof cases[caseKey] === 'string') {
|
||||
@@ -48,7 +48,7 @@ function SwitchContent({ expression, cases, className }) {
|
||||
{getContent(expression)}
|
||||
</TransitionReplace>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
SwitchContent.propTypes = {
|
||||
expression: PropTypes.string,
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.jump-nav-sm {
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
.jump-nav {
|
||||
@media (min-width: map-get($grid-breakpoints, "sm")) {
|
||||
padding-top: 1rem;
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: .5rem;
|
||||
@@ -30,16 +29,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
@extend .h4;
|
||||
margin-bottom: map-get($spacers, 3);
|
||||
}
|
||||
|
||||
.account-section {
|
||||
// These properties together will shift the hashlink position
|
||||
margin-bottom: map-get($spacers, 5);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.custom-switch {
|
||||
padding: 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
Form,
|
||||
ModalDialog,
|
||||
StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
} from '@openedx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
@@ -21,26 +21,20 @@ import { certPreferenceSelector } from '../data/selectors';
|
||||
import commonMessages from '../AccountSettingsPage.messages';
|
||||
import messages from './messages';
|
||||
|
||||
function CertificatePreference({
|
||||
const CertificatePreference = ({
|
||||
intl,
|
||||
fieldName,
|
||||
originalFullName,
|
||||
originalVerifiedName,
|
||||
saveState,
|
||||
useVerifiedNameForCerts,
|
||||
verifiedNameEnabled,
|
||||
}) {
|
||||
if (!verifiedNameEnabled || !originalVerifiedName) {
|
||||
// If the user doesn't have an approved verified name, do not display this component
|
||||
return null;
|
||||
}
|
||||
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const formId = 'useVerifiedNameForCerts';
|
||||
|
||||
function handleCheckboxChange() {
|
||||
const handleCheckboxChange = () => {
|
||||
if (!checked) {
|
||||
if (fieldName === 'verified_name') {
|
||||
dispatch(updateDraft(formId, true));
|
||||
@@ -50,22 +44,22 @@ function CertificatePreference({
|
||||
} else {
|
||||
setModalIsOpen(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleCancel() {
|
||||
const handleCancel = () => {
|
||||
setModalIsOpen(false);
|
||||
dispatch(resetDrafts());
|
||||
}
|
||||
};
|
||||
|
||||
function handleModalChange(e) {
|
||||
const handleModalChange = (e) => {
|
||||
if (e.target.value === 'fullName') {
|
||||
dispatch(updateDraft(formId, false));
|
||||
} else {
|
||||
dispatch(updateDraft(formId, true));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleSubmit(e) {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (saveState === 'pending') {
|
||||
@@ -73,24 +67,30 @@ function CertificatePreference({
|
||||
}
|
||||
|
||||
dispatch(saveSettings(formId, useVerifiedNameForCerts));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (fieldName === 'verified_name') {
|
||||
setChecked(useVerifiedNameForCerts);
|
||||
} else {
|
||||
setChecked(!useVerifiedNameForCerts);
|
||||
if (originalVerifiedName) {
|
||||
if (fieldName === 'verified_name') {
|
||||
setChecked(useVerifiedNameForCerts);
|
||||
} else {
|
||||
setChecked(!useVerifiedNameForCerts);
|
||||
}
|
||||
}
|
||||
}, [useVerifiedNameForCerts]);
|
||||
}, [originalVerifiedName, fieldName, useVerifiedNameForCerts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalIsOpen && saveState === 'complete') {
|
||||
setModalIsOpen(false);
|
||||
dispatch(closeForm(fieldName));
|
||||
if (originalVerifiedName) {
|
||||
if (modalIsOpen && saveState === 'complete') {
|
||||
setModalIsOpen(false);
|
||||
dispatch(closeForm(fieldName));
|
||||
}
|
||||
}
|
||||
}, [modalIsOpen, saveState]);
|
||||
}, [dispatch, originalVerifiedName, fieldName, modalIsOpen, saveState]);
|
||||
|
||||
return (
|
||||
// If the user doesn't have an approved verified name, do not display this component
|
||||
|
||||
return originalVerifiedName ? (
|
||||
<>
|
||||
<Form.Checkbox className="mt-1 mb-4" checked={checked} onChange={handleCheckboxChange}>
|
||||
{intl.formatMessage(messages['account.settings.field.name.checkbox.certificate.select'])}
|
||||
@@ -151,8 +151,8 @@ function CertificatePreference({
|
||||
</Form>
|
||||
</ModalDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
) : null;
|
||||
};
|
||||
|
||||
CertificatePreference.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
@@ -161,7 +161,6 @@ CertificatePreference.propTypes = {
|
||||
originalVerifiedName: PropTypes.string,
|
||||
saveState: PropTypes.string,
|
||||
useVerifiedNameForCerts: PropTypes.bool,
|
||||
verifiedNameEnabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
CertificatePreference.defaultProps = {
|
||||
@@ -169,7 +168,6 @@ CertificatePreference.defaultProps = {
|
||||
originalVerifiedName: '',
|
||||
saveState: null,
|
||||
useVerifiedNameForCerts: false,
|
||||
verifiedNameEnabled: false,
|
||||
};
|
||||
|
||||
export default connect(certPreferenceSelector)(injectIntl(CertificatePreference));
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable no-import-assign */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -27,8 +27,6 @@ jest.mock('react-redux', () => ({
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const IntlCertificatePreference = injectIntl(CertificatePreference);
|
||||
|
||||
const mockStore = configureStore();
|
||||
@@ -41,7 +39,7 @@ describe('NameChange', () => {
|
||||
const labelText = 'If checked, this name will appear on your certificates and public-facing records.';
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<Router history={history}>
|
||||
<Router>
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
@@ -56,7 +54,6 @@ describe('NameChange', () => {
|
||||
originalVerifiedName: 'edX Verified',
|
||||
saveState: null,
|
||||
useVerifiedNameForCerts: false,
|
||||
verifiedNameEnabled: true,
|
||||
intl: {},
|
||||
};
|
||||
|
||||
@@ -154,7 +151,7 @@ describe('NameChange', () => {
|
||||
const submitButton = screen.getByText('Choose name');
|
||||
fireEvent.click(submitButton);
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
payload: { formId, commitValues: false },
|
||||
payload: { formId, commitValues: false, extendedProfile: {} },
|
||||
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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:
|
||||
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.object,
|
||||
}).isRequired,
|
||||
confirmationValues: PropTypes.shape({
|
||||
coaching: PropTypes.object,
|
||||
name: PropTypes.object,
|
||||
phone_number: PropTypes.object,
|
||||
}).isRequired,
|
||||
fetchSettings: PropTypes.func.isRequired,
|
||||
profileDataManager: PropTypes.string,
|
||||
};
|
||||
|
||||
export default connect(coachingConsentPageSelector, {
|
||||
fetchSettings,
|
||||
})(injectIntl(CoachingConsent));
|
||||
@@ -1,66 +0,0 @@
|
||||
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;
|
||||
@@ -1,131 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Input, 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>
|
||||
<Input
|
||||
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>
|
||||
<Input
|
||||
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);
|
||||
@@ -1,98 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { ValidationFormGroup, Input } 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);
|
||||
}}
|
||||
/>
|
||||
<ValidationFormGroup
|
||||
for="coachingConsent"
|
||||
helpText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
|
||||
invalid={!!props.error}
|
||||
invalidMessage={props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
|
||||
className="custom-control custom-switch"
|
||||
>
|
||||
<Input
|
||||
name={props.name}
|
||||
className="custom-control-input"
|
||||
disabled={props.saveState === 'pending'}
|
||||
type="checkbox"
|
||||
id="coachingConsent"
|
||||
checked={props.coaching.coaching_consent}
|
||||
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,
|
||||
eligible_for_coaching,
|
||||
coaching_consent: e.target.checked,
|
||||
};
|
||||
props.saveSettings(name, value);
|
||||
}}
|
||||
/>
|
||||
<label className="custom-control-label" htmlFor="coachingConsent">{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
|
||||
</ValidationFormGroup>
|
||||
</>
|
||||
);
|
||||
|
||||
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));
|
||||
@@ -1,31 +0,0 @@
|
||||
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;
|
||||
@@ -1,51 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -1,278 +0,0 @@
|
||||
// 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>
|
||||
<span>
|
||||
Your name is managed by
|
||||
<b>
|
||||
test person
|
||||
</b>
|
||||
. Contact your administrator for help.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="alert-warning mb-2"
|
||||
>
|
||||
|
||||
</div>
|
||||
<label
|
||||
className="h6"
|
||||
htmlFor="fullName"
|
||||
>
|
||||
Please confirm your name
|
||||
</label>
|
||||
<input
|
||||
className="form-control"
|
||||
defaultValue="edx edx"
|
||||
disabled={true}
|
||||
id="fullName"
|
||||
name="full-name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="py-3"
|
||||
>
|
||||
<div
|
||||
className="alert-warning mb-2"
|
||||
>
|
||||
|
||||
</div>
|
||||
<label
|
||||
className="h6"
|
||||
htmlFor="phoneNumber"
|
||||
>
|
||||
Enter your mobile number
|
||||
</label>
|
||||
<input
|
||||
className="form-control"
|
||||
defaultValue="1234567890"
|
||||
id="phoneNumber"
|
||||
name="phone_number"
|
||||
type="text"
|
||||
/>
|
||||
</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="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>
|
||||
<input
|
||||
className="form-control"
|
||||
defaultValue="edx edx"
|
||||
disabled={false}
|
||||
id="fullName"
|
||||
name="full-name"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="py-3"
|
||||
>
|
||||
<div
|
||||
className="alert-warning mb-2"
|
||||
>
|
||||
|
||||
</div>
|
||||
<label
|
||||
className="h6"
|
||||
htmlFor="phoneNumber"
|
||||
>
|
||||
Enter your mobile number
|
||||
</label>
|
||||
<input
|
||||
className="form-control"
|
||||
defaultValue="1234567890"
|
||||
id="phoneNumber"
|
||||
name="phone_number"
|
||||
type="text"
|
||||
/>
|
||||
</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="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>
|
||||
`;
|
||||
@@ -77,9 +77,9 @@ export const beginNameChange = (formId) => ({
|
||||
});
|
||||
// SAVE SETTINGS ACTIONS
|
||||
|
||||
export const saveSettings = (formId, commitValues) => ({
|
||||
export const saveSettings = (formId, commitValues, extendedProfile = {}) => ({
|
||||
type: SAVE_SETTINGS.BASE,
|
||||
payload: { formId, commitValues },
|
||||
payload: { formId, commitValues, extendedProfile },
|
||||
});
|
||||
|
||||
export const saveSettingsBegin = () => ({
|
||||
|
||||
@@ -10,6 +10,11 @@ export const YEAR_OF_BIRTH_OPTIONS = (() => {
|
||||
return years.reverse();
|
||||
})();
|
||||
|
||||
export const COPPA_COMPLIANCE_YEAR = (() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
return currentYear - 13;
|
||||
})();
|
||||
|
||||
export const EDUCATION_LEVELS = [
|
||||
'',
|
||||
'p',
|
||||
@@ -20,7 +25,7 @@ export const EDUCATION_LEVELS = [
|
||||
'jhs',
|
||||
'el',
|
||||
'none',
|
||||
'o',
|
||||
'other',
|
||||
];
|
||||
|
||||
export const GENDER_OPTIONS = [
|
||||
@@ -29,6 +34,21 @@ export const GENDER_OPTIONS = [
|
||||
'm',
|
||||
'o',
|
||||
];
|
||||
export const WORK_EXPERIENCE_OPTIONS = [
|
||||
'',
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'10+',
|
||||
|
||||
];
|
||||
|
||||
export const COUNTRY_WITH_STATES = 'US';
|
||||
|
||||
|
||||
@@ -39,10 +39,9 @@ export const defaultState = {
|
||||
verifiedName: null,
|
||||
mostRecentVerifiedName: {},
|
||||
verifiedNameHistory: {},
|
||||
verifiedNameEnabled: false,
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
const reducer = (state = defaultState, action = {}) => {
|
||||
let dispatcherIsOpenForm;
|
||||
|
||||
switch (action.type) {
|
||||
|
||||
@@ -83,8 +83,8 @@ export function* handleSaveSettings(action) {
|
||||
yield put(saveSettingsBegin());
|
||||
|
||||
const { username, userId } = getAuthenticatedUser();
|
||||
const { commitValues, formId } = action.payload;
|
||||
const commitData = { [formId]: commitValues };
|
||||
const { commitValues, formId, extendedProfile } = action.payload;
|
||||
const commitData = Object.keys(extendedProfile).length > 0 ? extendedProfile : { [formId]: commitValues };
|
||||
let savedValues = null;
|
||||
if (formId === 'siteLanguage') {
|
||||
const previousSiteLanguage = getLocale();
|
||||
|
||||
@@ -12,7 +12,6 @@ const verifiedNameSettingsSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => ({
|
||||
history: accountSettings.verifiedNameHistory.results,
|
||||
verifiedNameEnabled: accountSettings?.verifiedNameHistory.verified_name_enabled,
|
||||
useVerifiedNameForCerts: accountSettings?.verifiedNameHistory.use_verified_name_for_certs,
|
||||
}),
|
||||
);
|
||||
@@ -107,11 +106,6 @@ const isEditingSelector = createSelector(
|
||||
(name, accountSettings) => accountSettings.openFormId === name,
|
||||
);
|
||||
|
||||
const confirmationValuesSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => accountSettings.confirmationValues,
|
||||
);
|
||||
|
||||
const errorSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => accountSettings.errors,
|
||||
@@ -162,7 +156,7 @@ function chooseFormValue(draft, committed) {
|
||||
return draft !== undefined ? draft : committed;
|
||||
}
|
||||
|
||||
const formValuesSelector = createSelector(
|
||||
export const formValuesSelector = createSelector(
|
||||
valuesSelector,
|
||||
draftsSelector,
|
||||
(values, drafts) => {
|
||||
@@ -170,6 +164,20 @@ const formValuesSelector = createSelector(
|
||||
Object.entries(values).forEach(([name, value]) => {
|
||||
if (typeof value === 'boolean') {
|
||||
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 {
|
||||
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
||||
}
|
||||
@@ -180,7 +188,7 @@ const formValuesSelector = createSelector(
|
||||
|
||||
const transformTimeZonesToOptions = timeZoneArr => timeZoneArr
|
||||
.map(({ time_zone, description }) => ({ // eslint-disable-line camelcase
|
||||
value: time_zone, label: description,
|
||||
value: time_zone, label: description, // eslint-disable-line camelcase
|
||||
}));
|
||||
|
||||
const timeZonesSelector = createSelector(
|
||||
@@ -229,7 +237,6 @@ export const accountSettingsPageSelector = createSelector(
|
||||
mostRecentApprovedVerifiedNameValueSelector,
|
||||
mostRecentVerifiedNameSelector,
|
||||
sortedVerifiedNameHistorySelector,
|
||||
verifiedNameSettingsSelector,
|
||||
(
|
||||
accountSettings,
|
||||
siteLanguageOptions,
|
||||
@@ -247,7 +254,6 @@ export const accountSettingsPageSelector = createSelector(
|
||||
verifiedName,
|
||||
mostRecentVerifiedName,
|
||||
verifiedNameHistory,
|
||||
verifiedNameSettings,
|
||||
) => ({
|
||||
siteLanguageOptions,
|
||||
siteLanguage,
|
||||
@@ -268,19 +274,16 @@ export const accountSettingsPageSelector = createSelector(
|
||||
verifiedName,
|
||||
mostRecentVerifiedName,
|
||||
verifiedNameHistory,
|
||||
verifiedNameEnabled: verifiedNameSettings?.verifiedNameEnabled,
|
||||
}),
|
||||
);
|
||||
|
||||
export const certPreferenceSelector = createSelector(
|
||||
verifiedNameSettingsSelector,
|
||||
valuesSelector,
|
||||
formValuesSelector,
|
||||
mostRecentApprovedVerifiedNameValueSelector,
|
||||
saveStateSelector,
|
||||
errorSelector,
|
||||
(
|
||||
verifiedNameSettings,
|
||||
committedValues,
|
||||
formValues,
|
||||
mostRecentApprovedVerifiedNameValue,
|
||||
@@ -291,36 +294,6 @@ export const certPreferenceSelector = createSelector(
|
||||
originalVerifiedName: mostRecentApprovedVerifiedNameValue?.verified_name || '',
|
||||
useVerifiedNameForCerts: formValues.useVerifiedNameForCerts || false,
|
||||
saveState,
|
||||
verifiedNameEnabled: verifiedNameSettings.verifiedNameEnabled || false,
|
||||
formErrors: errors,
|
||||
}),
|
||||
);
|
||||
|
||||
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,
|
||||
}),
|
||||
);
|
||||
|
||||
72
src/account-settings/data/selectors.test.js
Normal file
72
src/account-settings/data/selectors.test.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,6 @@ import isEmpty from 'lodash.isempty';
|
||||
import { handleRequestError, unpackFieldErrors } from './utils';
|
||||
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
||||
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
|
||||
import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service';
|
||||
import { getDemographics, getDemographicsOptions, patchDemographics } from '../demographics/data/service';
|
||||
import { DEMOGRAPHICS_FIELDS } from '../demographics/data/utils';
|
||||
|
||||
@@ -177,19 +176,6 @@ export async function shouldDisplayDemographicsQuestions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function getVerifiedNameEnabled() {
|
||||
let data;
|
||||
const client = getAuthenticatedHttpClient();
|
||||
try {
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name_enabled`;
|
||||
({ data } = await client.get(requestUrl));
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getVerifiedName() {
|
||||
let data;
|
||||
const client = getAuthenticatedHttpClient();
|
||||
@@ -227,7 +213,7 @@ export async function postVerifiedName(data) {
|
||||
|
||||
/**
|
||||
* A single function to GET everything considered a setting.
|
||||
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
|
||||
* Currently encapsulates Account, Preferences, ThirdPartyAuth, and Demographics
|
||||
*/
|
||||
export async function getSettings(username, userRoles, userId) {
|
||||
const [
|
||||
@@ -236,7 +222,6 @@ export async function getSettings(username, userRoles, userId) {
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
coaching,
|
||||
shouldDisplayDemographicsQuestionsResponse,
|
||||
demographics,
|
||||
demographicsOptions,
|
||||
@@ -246,7 +231,6 @@ export async function getSettings(username, userRoles, userId) {
|
||||
getThirdPartyAuthProviders(),
|
||||
getProfileDataManager(username, userRoles),
|
||||
getTimeZones(),
|
||||
getConfig().COACHING_ENABLED && getCoachingPreferences(userId),
|
||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && shouldDisplayDemographicsQuestions(),
|
||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
|
||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographicsOptions(),
|
||||
@@ -258,7 +242,6 @@ export async function getSettings(username, userRoles, userId) {
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
coaching,
|
||||
shouldDisplayDemographicsSection: shouldDisplayDemographicsQuestionsResponse,
|
||||
...demographics,
|
||||
demographicsOptions,
|
||||
@@ -267,26 +250,23 @@ export async function getSettings(username, userRoles, userId) {
|
||||
|
||||
/**
|
||||
* A single function to PATCH everything considered a setting.
|
||||
* Currently encapsulates Account, Preferences, coaching and ThirdPartyAuth
|
||||
* Currently encapsulates Account, Preferences, ThirdPartyAuth
|
||||
*/
|
||||
export async function patchSettings(username, commitValues, userId) {
|
||||
// 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
|
||||
// user/v1/preferences where it does update. This is the one we use.
|
||||
const preferenceKeys = ['time_zone'];
|
||||
const coachingKeys = ['coaching'];
|
||||
const demographicsKeys = DEMOGRAPHICS_FIELDS;
|
||||
const certificateKeys = ['useVerifiedNameForCerts'];
|
||||
const isDemographicsKey = (value, key) => key.includes('demographics');
|
||||
const accountCommitValues = omit(
|
||||
commitValues,
|
||||
preferenceKeys,
|
||||
coachingKeys,
|
||||
demographicsKeys,
|
||||
certificateKeys,
|
||||
);
|
||||
const preferenceCommitValues = pick(commitValues, preferenceKeys);
|
||||
const coachingCommitValues = pick(commitValues, coachingKeys);
|
||||
const demographicsCommitValues = pickBy(commitValues, isDemographicsKey);
|
||||
const certCommitValues = pick(commitValues, certificateKeys);
|
||||
const patchRequests = [];
|
||||
@@ -297,9 +277,6 @@ export async function patchSettings(username, commitValues, userId) {
|
||||
if (!isEmpty(preferenceCommitValues)) {
|
||||
patchRequests.push(patchPreferences(username, preferenceCommitValues));
|
||||
}
|
||||
if (!isEmpty(coachingCommitValues)) {
|
||||
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
|
||||
}
|
||||
if (!isEmpty(demographicsCommitValues)) {
|
||||
patchRequests.push(patchDemographics(userId, demographicsCommitValues));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { put } from 'redux-saga/effects';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
import { history } from '@edx/frontend-platform';
|
||||
|
||||
export default function* handleFailure(error, failureAction = null, failureRedirectPath = null) {
|
||||
export default function* handleFailure(error, navigate, failureAction = null, failureRedirectPath = null) {
|
||||
if (error.fieldErrors && failureAction !== null) {
|
||||
yield put(failureAction({ fieldErrors: error.fieldErrors }));
|
||||
}
|
||||
@@ -11,6 +10,6 @@ export default function* handleFailure(error, failureAction = null, failureRedir
|
||||
yield put(failureAction(error.message));
|
||||
}
|
||||
if (failureRedirectPath !== null) {
|
||||
history.push(failureRedirectPath);
|
||||
navigate(failureRedirectPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
|
||||
// Messages
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
@@ -25,10 +25,12 @@ const BeforeProceedingBanner = (props) => {
|
||||
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'."
|
||||
values={{
|
||||
actionLink: (
|
||||
actionLink: supportArticleUrl ? (
|
||||
<Hyperlink destination={supportArticleUrl}>
|
||||
{intl.formatMessage(messages[instructionMessageId])}
|
||||
</Hyperlink>
|
||||
) : (
|
||||
intl.formatMessage(messages[instructionMessageId])
|
||||
),
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { IntlProvider, injectIntl, createIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
ReactDOM.createPortal = node => node;
|
||||
|
||||
import BeforeProceedingBanner from './BeforeProceedingBanner'; // eslint-disable-line import/first
|
||||
|
||||
const IntlBeforeProceedingBanner = injectIntl(BeforeProceedingBanner);
|
||||
|
||||
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',
|
||||
intl: createIntl({ locale: 'en' }),
|
||||
supportArticleUrl: '',
|
||||
};
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<IntlBeforeProceedingBanner
|
||||
{...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',
|
||||
intl: createIntl({ locale: 'en' }),
|
||||
supportArticleUrl: 'http://test-support.edx',
|
||||
};
|
||||
const tree = renderer
|
||||
.create((
|
||||
<IntlProvider locale="en">
|
||||
<IntlBeforeProceedingBanner
|
||||
{...props}
|
||||
/>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -2,12 +2,12 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
Button, Input, Modal, ValidationFormGroup,
|
||||
} from '@edx/paragon';
|
||||
AlertModal,
|
||||
Button, Input, ValidationFormGroup, ActionRow,
|
||||
} from '@openedx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import messages from './messages';
|
||||
import Alert from '../Alert';
|
||||
@@ -74,59 +74,58 @@ export class ConfirmationModal extends Component {
|
||||
: 'account.settings.delete.account.modal.text.2';
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
<AlertModal
|
||||
isOpen={open}
|
||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
||||
body={(
|
||||
<div>
|
||||
{this.renderError()}
|
||||
<Alert
|
||||
className="alert-warning mt-n2"
|
||||
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
|
||||
>
|
||||
<h6>
|
||||
{intl.formatMessage(
|
||||
messages['account.settings.delete.account.modal.text.1'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</h6>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
messages[deleteAccountModalText2MessageKey],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
</Alert>
|
||||
<ValidationFormGroup
|
||||
for={passwordFieldId}
|
||||
invalid={errorType !== null}
|
||||
invalidMessage={intl.formatMessage(invalidMessage)}
|
||||
>
|
||||
<label className="d-block" htmlFor={passwordFieldId}>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
||||
</label>
|
||||
<Input
|
||||
name="password"
|
||||
id={passwordFieldId}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
</div>
|
||||
)}
|
||||
buttons={[
|
||||
<Button variant="danger" onClick={onSubmit}>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.delete'])}
|
||||
</Button>,
|
||||
]}
|
||||
closeText={intl.formatMessage(messages['account.settings.delete.account.modal.confirm.cancel'])}
|
||||
renderHeaderCloseButton={false}
|
||||
onClose={onCancel}
|
||||
/>
|
||||
footerNode={(
|
||||
<ActionRow>
|
||||
<Button variant="link" onClick={onCancel}>Cancel</Button>
|
||||
<Button variant="danger" onClick={onSubmit}>Yes, Delete</Button>
|
||||
</ActionRow>
|
||||
)}
|
||||
>
|
||||
<div className="p-3">
|
||||
{this.renderError()}
|
||||
<Alert
|
||||
className="alert-warning mt-n2"
|
||||
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
|
||||
>
|
||||
<h6>
|
||||
{intl.formatMessage(
|
||||
messages['account.settings.delete.account.modal.text.1'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</h6>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
messages[deleteAccountModalText2MessageKey],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
</Alert>
|
||||
<ValidationFormGroup
|
||||
for={passwordFieldId}
|
||||
invalid={errorType !== null}
|
||||
invalidMessage={intl.formatMessage(invalidMessage)}
|
||||
>
|
||||
<label className="d-block" htmlFor={passwordFieldId}>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
||||
</label>
|
||||
<Input
|
||||
name="password"
|
||||
id={passwordFieldId}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
</div>
|
||||
|
||||
</AlertModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
import { Button, Hyperlink } from '@openedx/paragon';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
@@ -59,6 +59,7 @@ export class DeleteAccount extends React.Component {
|
||||
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
||||
} = this.props;
|
||||
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
|
||||
// to allow edx.org to fulfill its business requirements.
|
||||
@@ -66,9 +67,13 @@ export class DeleteAccount extends React.Component {
|
||||
? 'account.settings.delete.account.text.2.edX'
|
||||
: 'account.settings.delete.account.text.2';
|
||||
|
||||
const optInInstructionMessageId = getConfig().MARKETING_EMAILS_OPT_IN
|
||||
? 'account.settings.delete.account.please.confirm'
|
||||
: 'account.settings.delete.account.please.activate';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="section-heading">
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
||||
</h2>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
||||
@@ -88,8 +93,10 @@ export class DeleteAccount extends React.Component {
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
<p className="text-danger h6">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.warning'],
|
||||
{ siteName: getConfig().SITE_NAME })}
|
||||
{intl.formatMessage(
|
||||
messages['account.settings.delete.account.text.warning'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">
|
||||
@@ -108,15 +115,15 @@ export class DeleteAccount extends React.Component {
|
||||
|
||||
{isVerifiedAccount ? null : (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.activate"
|
||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
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="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
supportArticleUrl={supportArticleUrl}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
// Testing the modals separately, they just clutter up the snapshots if included here.
|
||||
jest.mock('./ConfirmationModal');
|
||||
jest.mock('./SuccessModal');
|
||||
jest.mock('./ConfirmationModal', () => function ConfirmationModalMock() {
|
||||
return <></>;
|
||||
});
|
||||
jest.mock('./SuccessModal', () => function SuccessModalMock() {
|
||||
return <></>;
|
||||
});
|
||||
|
||||
import { DeleteAccount } from './DeleteAccount'; // eslint-disable-line import/first
|
||||
|
||||
@@ -37,6 +42,7 @@ describe('DeleteAccount', () => {
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Modal } from '@edx/paragon';
|
||||
import { ModalLayer, ModalCloseButton } from '@openedx/paragon';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
export const SuccessModal = (props) => {
|
||||
const { status, intl, onClose } = props;
|
||||
return (
|
||||
<Modal
|
||||
open={status === 'deleted'}
|
||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.after.header'])}
|
||||
body={(
|
||||
<div>
|
||||
|
||||
<ModalLayer isOpen={status === 'deleted'} onClose={onClose}>
|
||||
<div className="mw-sm p-5 bg-white mx-auto my-3">
|
||||
<h3>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.after.header'])}
|
||||
</h3>
|
||||
<div className="p-3">
|
||||
<p className="h6">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.after.text'])}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
closeText={intl.formatMessage(messages['account.settings.delete.account.modal.after.button'])}
|
||||
renderHeaderCloseButton={false}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<p>
|
||||
<ModalCloseButton className="float-right" variant="link">Close</ModalCloseButton>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</ModalLayer>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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-exclamation-triangle fa-w-18 mr-2"
|
||||
data-icon="exclamation-triangle"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
style={Object {}}
|
||||
/>
|
||||
</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-exclamation-triangle fa-w-18 mr-2"
|
||||
data-icon="exclamation-triangle"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
Before proceeding, please
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://test-support.edx"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
unlink all social media accounts
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,254 +1,68 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id1"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id1"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-warning mt-n2"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
||||
data-icon="exclamation-triangle"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 576 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
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"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h6>
|
||||
You have selected "Delete My Account". Deletion of your account and personal data is permanent and cannot be undone. localhost will not be able to recover your account or the data that is deleted.
|
||||
</h6>
|
||||
<p>
|
||||
If you proceed, you will be unable to use this account to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<label
|
||||
className="d-block"
|
||||
htmlFor="passwordFieldId"
|
||||
>
|
||||
If you still wish to continue and delete your account, please enter your account password:
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
className="form-control"
|
||||
id="passwordFieldId"
|
||||
name="password"
|
||||
onChange={[MockFunction]}
|
||||
type="password"
|
||||
value="fluffy bunnies"
|
||||
/>
|
||||
<strong
|
||||
className="invalid-feedback"
|
||||
id="passwordFieldId-invalid-feedback"
|
||||
>
|
||||
Unable to delete account
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
disabled={false}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Yes, Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `null`;
|
||||
|
||||
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>,
|
||||
<div
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
className="pgn__modal-layer"
|
||||
data-focus-lock-disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id3"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
className="pgn__modal-content-container"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
className="pgn__modal-backdrop"
|
||||
data-testid="modal-backdrop"
|
||||
onClick={[MockFunction]}
|
||||
onKeyDown={[MockFunction]}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
aria-label="Are you sure?"
|
||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
className="pgn__modal-header"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
<h2
|
||||
className="pgn__modal-title"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id3"
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="pgn__modal-body pgn__modal-body-scroll-top pgn__modal-body-scroll-bottom"
|
||||
>
|
||||
<div />
|
||||
<div
|
||||
className="pgn__modal-body-content"
|
||||
>
|
||||
<div
|
||||
className="p-3"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-danger mt-n2"
|
||||
>
|
||||
@@ -312,14 +126,13 @@ Array [
|
||||
If you proceed, you will be unable to use this account to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="form-group"
|
||||
data-testid="validation-form-group"
|
||||
>
|
||||
<label
|
||||
className="d-block"
|
||||
@@ -345,13 +158,18 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
className="pgn__modal-footer"
|
||||
>
|
||||
<div
|
||||
className="modal-footer"
|
||||
className="pgn__action-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
@@ -367,99 +185,85 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>,
|
||||
<div
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
className="pgn__modal-layer"
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id2"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
className="pgn__modal-content-container"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
className="pgn__modal-backdrop"
|
||||
data-testid="modal-backdrop"
|
||||
onClick={[MockFunction]}
|
||||
onKeyDown={[MockFunction]}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
aria-label="Are you sure?"
|
||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
className="pgn__modal-header"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
<h2
|
||||
className="pgn__modal-title"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="pgn__modal-body pgn__modal-body-scroll-top pgn__modal-body-scroll-bottom"
|
||||
>
|
||||
<div />
|
||||
<div
|
||||
className="pgn__modal-body-content"
|
||||
>
|
||||
<div
|
||||
className="p-3"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-warning mt-n2"
|
||||
>
|
||||
@@ -490,14 +294,13 @@ Array [
|
||||
If you proceed, you will be unable to use this account to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="form-group"
|
||||
data-testid="validation-form-group"
|
||||
>
|
||||
<label
|
||||
className="d-block"
|
||||
@@ -523,13 +326,18 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
className="pgn__modal-footer"
|
||||
>
|
||||
<div
|
||||
className="modal-footer"
|
||||
className="pgn__action-row"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
@@ -545,22 +353,22 @@ Array [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
<div>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Delete My Account
|
||||
</h2>
|
||||
@@ -17,9 +17,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
Once your account is deleted, you cannot use it to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</p>
|
||||
<p
|
||||
className="text-danger h6"
|
||||
@@ -28,7 +26,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
@@ -52,7 +50,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
<div>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Delete My Account
|
||||
</h2>
|
||||
@@ -66,9 +64,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
Once your account is deleted, you cannot use it to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</p>
|
||||
<p
|
||||
className="text-danger h6"
|
||||
@@ -77,7 +73,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
@@ -118,18 +114,16 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
activate your account
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
activate your account
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,7 +132,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
<div>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Delete My Account
|
||||
</h2>
|
||||
@@ -152,9 +146,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
Once your account is deleted, you cannot use it to take courses on localhost.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</span>
|
||||
You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.
|
||||
</p>
|
||||
<p
|
||||
className="text-danger h6"
|
||||
@@ -163,7 +155,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
@@ -204,18 +196,16 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
unlink all social media accounts
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
unlink all social media accounts
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,563 +1,90 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id1"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id1"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
exports[`SuccessModal should match default closed success modal snapshot 1`] = `null`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 2`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id2"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
exports[`SuccessModal should match default closed success modal snapshot 2`] = `null`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id3"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id3"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
exports[`SuccessModal should match default closed success modal snapshot 3`] = `null`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id4"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id4"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `null`;
|
||||
|
||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>,
|
||||
<div
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
className="pgn__modal-layer"
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id5"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
className="pgn__modal-content-container"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
className="pgn__modal-backdrop"
|
||||
data-testid="modal-backdrop"
|
||||
onClick={[MockFunction]}
|
||||
onKeyDown={[MockFunction]}
|
||||
/>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={1}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
className="mw-sm p-5 bg-white mx-auto my-3"
|
||||
>
|
||||
<h3>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h3>
|
||||
<div
|
||||
className="modal-content"
|
||||
className="p-3"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id5"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<button
|
||||
className="pgn__modal-close-button float-right btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={-1}
|
||||
/>,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export { default } from './DeleteAccount';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
|
||||
@@ -51,6 +51,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'activate your account',
|
||||
description: 'This is the text on a link that goes to the support page. It is part of this sentence: Before proceeding, please activate your account.',
|
||||
},
|
||||
'account.settings.delete.account.please.confirm': {
|
||||
id: 'account.settings.delete.account.please.confirm',
|
||||
defaultMessage: 'confirm your account',
|
||||
description: 'This is the text on a link that goes to the support page. It is part of this sentence: Before proceeding, please confirm your account.',
|
||||
},
|
||||
'account.settings.delete.account.please.unlink': {
|
||||
id: 'account.settings.delete.account.please.unlink',
|
||||
defaultMessage: 'unlink all social media accounts',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CheckBox } from '@edx/paragon';
|
||||
import { Form } from '@openedx/paragon';
|
||||
import { DECLINED } from '../data/constants';
|
||||
|
||||
const Checkboxes = (props) => {
|
||||
@@ -14,7 +14,7 @@ const Checkboxes = (props) => {
|
||||
const [selected, setSelected] = useState(values);
|
||||
useEffect(() => {
|
||||
onChange(id, selected);
|
||||
}, [selected]);
|
||||
}, [id, onChange, selected]);
|
||||
|
||||
const handleToggle = (value, option) => {
|
||||
// If the user checked 'declined', uncheck all other options
|
||||
@@ -40,16 +40,17 @@ const Checkboxes = (props) => {
|
||||
const isChecked = selected.includes(option.value);
|
||||
return (
|
||||
<div key={option.value} className="checkboxOption">
|
||||
<CheckBox
|
||||
<Form.Checkbox
|
||||
type="checkbox"
|
||||
id={option.value}
|
||||
name={option.value}
|
||||
value={option.value}
|
||||
checked={isChecked}
|
||||
autoFocus={isFirst}
|
||||
label={option.label}
|
||||
onChange={(value) => handleToggle(value, option.value)}
|
||||
/>
|
||||
onChange={(event) => handleToggle(event.target.checked, option.value)}
|
||||
>
|
||||
{option.label}
|
||||
</Form.Checkbox>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
intlShape,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Hyperlink, Input } from '@edx/paragon';
|
||||
import { Hyperlink, Form } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -13,7 +13,7 @@ import get from 'lodash.get';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import memoize from 'memoize-one';
|
||||
import { demographicsSectionSelector } from '../data/selectors';
|
||||
import EditableField from '../EditableField';
|
||||
import EditableSelectField from '../EditableSelectField';
|
||||
import Checkboxes from './Checkboxes';
|
||||
import Alert from '../Alert';
|
||||
import { saveMultipleSettings, updateDraft } from '../data/actions';
|
||||
@@ -75,7 +75,7 @@ class DemographicsSection extends React.Component {
|
||||
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
|
||||
return matchingOption && matchingOption.label;
|
||||
}).join(', ');
|
||||
}
|
||||
};
|
||||
|
||||
handleEditableFieldChange = (name, value) => {
|
||||
this.props.updateDraft(name, value);
|
||||
@@ -162,8 +162,8 @@ class DemographicsSection extends React.Component {
|
||||
const showWorkStatusDescribe = this.props.formValues.demographics_work_status === OTHER;
|
||||
|
||||
return (
|
||||
<div className="account-section" id="demographics-information" ref={this.props.forwardRef}>
|
||||
<h2 className="section-heading">
|
||||
<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>
|
||||
@@ -188,7 +188,7 @@ class DemographicsSection extends React.Component {
|
||||
*/}
|
||||
{this.hasRetrievedDemographicsOptions() && (
|
||||
<div id="demographics-fields">
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_gender"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_gender}
|
||||
@@ -199,7 +199,7 @@ class DemographicsSection extends React.Component {
|
||||
{...editableFieldProps}
|
||||
>
|
||||
{showSelfDescribe && (
|
||||
<Input
|
||||
<Form.Control
|
||||
name="demographics_gender_description"
|
||||
id="field-demographics_gender_description"
|
||||
type="text"
|
||||
@@ -210,8 +210,8 @@ class DemographicsSection extends React.Component {
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
</EditableField>
|
||||
<EditableField
|
||||
</EditableSelectField>
|
||||
<EditableSelectField
|
||||
name="demographics_user_ethnicity"
|
||||
type="select"
|
||||
hidden
|
||||
@@ -226,8 +226,8 @@ class DemographicsSection extends React.Component {
|
||||
values={this.props.formValues.demographics_user_ethnicity}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
</EditableField>
|
||||
<EditableField
|
||||
</EditableSelectField>
|
||||
<EditableSelectField
|
||||
name="demographics_income"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_income}
|
||||
@@ -236,7 +236,7 @@ class DemographicsSection extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.income.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_military_history"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_military_history}
|
||||
@@ -245,7 +245,7 @@ class DemographicsSection extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_learner_education_level"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_learner_education_level}
|
||||
@@ -254,7 +254,7 @@ class DemographicsSection extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_parent_education_level"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_parent_education_level}
|
||||
@@ -263,7 +263,7 @@ class DemographicsSection extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_work_status"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_work_status}
|
||||
@@ -276,7 +276,7 @@ class DemographicsSection extends React.Component {
|
||||
{...editableFieldProps}
|
||||
>
|
||||
{showWorkStatusDescribe && (
|
||||
<Input
|
||||
<Form.Control
|
||||
name="demographics_work_status_description"
|
||||
id="field-demographics_work_status_description"
|
||||
type="text"
|
||||
@@ -287,8 +287,8 @@ class DemographicsSection extends React.Component {
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
</EditableField>
|
||||
<EditableField
|
||||
</EditableSelectField>
|
||||
<EditableSelectField
|
||||
name="demographics_current_work_sector"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_current_work_sector}
|
||||
@@ -297,7 +297,7 @@ class DemographicsSection extends React.Component {
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
<EditableSelectField
|
||||
name="demographics_future_work_sector"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_future_work_sector}
|
||||
@@ -317,7 +317,7 @@ DemographicsSection.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
formValues: PropTypes.shape({
|
||||
demographics_gender: PropTypes.string,
|
||||
demographics_user_ethnicity: PropTypes.array,
|
||||
demographics_user_ethnicity: PropTypes.shape([]),
|
||||
demographics_income: PropTypes.string,
|
||||
demographics_military_history: PropTypes.string,
|
||||
demographics_learner_education_level: PropTypes.string,
|
||||
@@ -327,11 +327,11 @@ DemographicsSection.propTypes = {
|
||||
demographics_future_work_sector: PropTypes.string,
|
||||
demographics_work_status_description: PropTypes.string,
|
||||
demographics_gender_description: PropTypes.string,
|
||||
demographicsOptions: PropTypes.object,
|
||||
demographicsOptions: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
drafts: PropTypes.shape({
|
||||
demographics_gender: PropTypes.string,
|
||||
demographics_user_ethnicity: PropTypes.array,
|
||||
demographics_user_ethnicity: PropTypes.shape([]),
|
||||
demographics_income: PropTypes.string,
|
||||
demographics_military_history: PropTypes.string,
|
||||
demographics_learner_education_level: PropTypes.string,
|
||||
@@ -341,7 +341,7 @@ DemographicsSection.propTypes = {
|
||||
demographics_future_work_sector: PropTypes.string,
|
||||
demographics_work_status_description: PropTypes.string,
|
||||
demographics_gender_description: PropTypes.string,
|
||||
demographicsOptions: PropTypes.object,
|
||||
demographicsOptions: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
formErrors: PropTypes.shape({
|
||||
demographicsError: PropTypes.string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-import-assign */
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
@@ -2,29 +2,30 @@
|
||||
|
||||
exports[`DemographicsSection should render 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -34,7 +35,6 @@ exports[`DemographicsSection should render 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -44,10 +44,15 @@ exports[`DemographicsSection should render 1`] = `
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -644,29 +649,30 @@ exports[`DemographicsSection should render 1`] = `
|
||||
|
||||
exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -676,7 +682,6 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -686,10 +691,15 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -702,9 +712,7 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
<span>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</span>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1300,29 +1308,30 @@ exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
|
||||
exports[`DemographicsSection should render an Alert when demographicsOptions props are empty 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -1332,7 +1341,6 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -1342,10 +1350,15 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -1358,9 +1371,7 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
<span>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</span>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1369,29 +1380,30 @@ exports[`DemographicsSection should render an Alert when demographicsOptions pro
|
||||
|
||||
exports[`DemographicsSection should render ethnicity correctly when multiple options are selected 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -1401,7 +1413,6 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -1411,10 +1422,15 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -2004,29 +2020,30 @@ exports[`DemographicsSection should render ethnicity correctly when multiple opt
|
||||
|
||||
exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -2036,7 +2053,6 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -2046,10 +2062,15 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -2639,29 +2660,30 @@ exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||
|
||||
exports[`DemographicsSection should set user input correctly when user provides answers to work_status question 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -2671,7 +2693,6 @@ exports[`DemographicsSection should set user input correctly when user provides
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -2681,10 +2702,15 @@ exports[`DemographicsSection should set user input correctly when user provides
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
@@ -3281,29 +3307,30 @@ exports[`DemographicsSection should set user input correctly when user provides
|
||||
|
||||
exports[`DemographicsSection should set user input correctly when user provides gender self-description 1`] = `
|
||||
<div
|
||||
className="account-section"
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading"
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="default-link standalone-link"
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="d-inline-block align-text-top"
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
|
||||
<span
|
||||
className="pgn__icon"
|
||||
data-testid="hyperlink-icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
@@ -3313,7 +3340,6 @@ exports[`DemographicsSection should set user input correctly when user provides
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
aria-label=""
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
@@ -3323,10 +3349,15 @@ exports[`DemographicsSection should set user input correctly when user provides
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7ZM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
19
src/account-settings/hoc.jsx
Normal file
19
src/account-settings/hoc.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
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;
|
||||
};
|
||||
38
src/account-settings/hoc.test.jsx
Normal file
38
src/account-settings/hoc.test.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
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,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export { default } from './AccountSettingsPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Form,
|
||||
ModalDialog,
|
||||
StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import { closeForm, saveSettingsReset } from '../data/actions';
|
||||
import { nameChangeSelector } from '../data/selectors';
|
||||
@@ -21,35 +21,35 @@ import { nameChangeSelector } from '../data/selectors';
|
||||
import { requestNameChange, requestNameChangeFailure, requestNameChangeReset } from './data/actions';
|
||||
import messages from './messages';
|
||||
|
||||
function NameChangeModal({
|
||||
const NameChangeModal = ({
|
||||
targetFormId,
|
||||
errors,
|
||||
formValues,
|
||||
intl,
|
||||
saveState,
|
||||
}) {
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { push } = useHistory();
|
||||
const navigate = useNavigate();
|
||||
const { username } = getAuthenticatedUser();
|
||||
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
||||
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
||||
|
||||
function resetLocalState() {
|
||||
const resetLocalState = useCallback(() => {
|
||||
setConfirmedWarning(false);
|
||||
dispatch(requestNameChangeReset());
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
function handleChange(e) {
|
||||
const handleChange = (e) => {
|
||||
setVerifiedNameInput(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
function handleClose() {
|
||||
const handleClose = useCallback(() => {
|
||||
resetLocalState();
|
||||
dispatch(closeForm(targetFormId));
|
||||
dispatch(saveSettingsReset());
|
||||
}
|
||||
}, [dispatch, resetLocalState, targetFormId]);
|
||||
|
||||
function handleSubmit(e) {
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (saveState === 'pending') {
|
||||
@@ -64,14 +64,14 @@ function NameChangeModal({
|
||||
const draftProfileName = targetFormId === 'name' ? formValues.name : null;
|
||||
dispatch(requestNameChange(username, draftProfileName, verifiedNameInput));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (saveState === 'complete') {
|
||||
handleClose();
|
||||
push('/id-verification');
|
||||
navigate(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
||||
}
|
||||
}, [saveState]);
|
||||
}, [handleClose, navigate, saveState]);
|
||||
|
||||
function renderErrors() {
|
||||
if (Object.keys(errors).length > 0) {
|
||||
@@ -183,7 +183,7 @@ function NameChangeModal({
|
||||
|
||||
</ModalDialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
NameChangeModal.propTypes = {
|
||||
targetFormId: PropTypes.string.isRequired,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export { default } from './NameChange';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
|
||||
@@ -23,12 +23,12 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.name.change.id.name.label': {
|
||||
id: 'account.settings.name.change.id.name.label',
|
||||
defaultMessage: 'Enter your name as it appears on your government-issued ID.',
|
||||
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.name.change.id.name.placeholder': {
|
||||
id: 'account.settings.name.change.id.name.placeholder',
|
||||
defaultMessage: 'Enter the name on your government ID',
|
||||
defaultMessage: 'Enter the name on your photo ID',
|
||||
description: 'Form label instructing the user to enter the name on their ID.',
|
||||
},
|
||||
'account.settings.name.change.error.valid.name': {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable no-import-assign */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import {
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -27,8 +27,6 @@ jest.mock('react-redux', () => ({
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ nameChangeSelector: () => ({}) })));
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const IntlNameChange = injectIntl(NameChange);
|
||||
|
||||
const mockStore = configureStore();
|
||||
@@ -38,7 +36,7 @@ describe('NameChange', () => {
|
||||
let store = {};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<Router history={history}>
|
||||
<Router>
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
@@ -70,7 +68,7 @@ describe('NameChange', () => {
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it('renders populated input after clicking continue if verified_name in form data', async () => {
|
||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your government ID');
|
||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
expect(getInput()).toBeNull();
|
||||
@@ -82,7 +80,7 @@ describe('NameChange', () => {
|
||||
});
|
||||
|
||||
it('renders empty input after clicking continue if verified_name not in form data', async () => {
|
||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your government ID');
|
||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
||||
const formProps = {
|
||||
...props,
|
||||
formValues: {
|
||||
@@ -112,7 +110,7 @@ describe('NameChange', () => {
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your government ID');
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
@@ -139,7 +137,7 @@ describe('NameChange', () => {
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your government ID');
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
@@ -155,7 +153,7 @@ describe('NameChange', () => {
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your government ID');
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
@@ -167,6 +165,6 @@ describe('NameChange', () => {
|
||||
props.saveState = 'complete';
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
expect(history.location.pathname).toEqual('/id-verification');
|
||||
expect(window.location.pathname).toEqual('/id-verification');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import { Hyperlink } from '@openedx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@@ -12,7 +13,7 @@ const ConfirmationAlert = (props) => {
|
||||
|
||||
const technicalSupportLink = (
|
||||
<Hyperlink
|
||||
destination="https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-"
|
||||
destination={getConfig().PASSWORD_RESET_SUPPORT_LINK}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { StatefulButton } from '@edx/paragon';
|
||||
import { StatefulButton } from '@openedx/paragon';
|
||||
|
||||
import { resetPassword } from './data/actions';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export { default } from './ResetPassword';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { RESET_PASSWORD } from './data/actions';
|
||||
|
||||
@@ -2,8 +2,9 @@ import { AsyncActionType } from '../data/utils';
|
||||
|
||||
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
||||
|
||||
export const fetchSiteLanguages = () => ({
|
||||
export const fetchSiteLanguages = handleNavigation => ({
|
||||
type: FETCH_SITE_LANGUAGES.BASE,
|
||||
payload: { handleNavigation },
|
||||
});
|
||||
|
||||
export const fetchSiteLanguagesBegin = () => ({
|
||||
|
||||
@@ -19,6 +19,11 @@ const siteLanguageList = [
|
||||
name: 'Español (Latinoamérica)',
|
||||
released: true,
|
||||
},
|
||||
{
|
||||
code: 'fa-ir',
|
||||
name: 'فارسی',
|
||||
released: true,
|
||||
},
|
||||
{
|
||||
code: 'fr',
|
||||
name: 'Français',
|
||||
@@ -69,6 +74,31 @@ const siteLanguageList = [
|
||||
name: '中文 (简体)',
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
export default siteLanguageList;
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
import { getSiteLanguageList } from './service';
|
||||
import { handleFailure } from '../data/utils';
|
||||
|
||||
function* handleFetchSiteLanguages() {
|
||||
function* handleFetchSiteLanguages(action) {
|
||||
try {
|
||||
yield put(fetchSiteLanguagesBegin());
|
||||
const siteLanguageList = yield call(getSiteLanguageList);
|
||||
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
||||
} catch (e) {
|
||||
yield call(handleFailure, e, fetchSiteLanguagesFailure);
|
||||
yield call(handleFailure, e, action.payload.handleNavigation, fetchSiteLanguagesFailure);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
src/account-settings/test/AccountSettingsPage.test.jsx
Normal file
101
src/account-settings/test/AccountSettingsPage.test.jsx
Normal file
@@ -0,0 +1,101 @@
|
||||
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';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||
sendTrackingLogEvent: jest.fn(),
|
||||
getCountryList: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-redux', () => ({
|
||||
...jest.requireActual('react-redux'),
|
||||
useDispatch: () => mockDispatch,
|
||||
}));
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
|
||||
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: '',
|
||||
},
|
||||
],
|
||||
|
||||
},
|
||||
fetchSettings: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
169
src/account-settings/test/EditableSelectField.test.jsx
Normal file
169
src/account-settings/test/EditableSelectField.test.jsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import React from 'react';
|
||||
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, injectIntl } 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 IntlEditableSelectField = injectIntl(EditableSelectField);
|
||||
|
||||
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(<IntlEditableSelectField {...props} />)).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders EditableSelectField correctly with editing enabled', () => {
|
||||
props = {
|
||||
...props,
|
||||
isEditing: true,
|
||||
};
|
||||
|
||||
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...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(<IntlEditableSelectField {...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(<IntlEditableSelectField {...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(<IntlEditableSelectField {...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(<IntlEditableSelectField {...propsWithGroups} />)).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,45 +1,65 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { AppProvider } from '@edx/frontend-platform/react';
|
||||
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 configureStore from '../../data/configureStore';
|
||||
|
||||
const IntlJumpNav = injectIntl(JumpNav);
|
||||
|
||||
describe('JumpNav', () => {
|
||||
mergeConfig({
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION: false,
|
||||
ENABLE_ACCOUNT_DELETION: true,
|
||||
});
|
||||
|
||||
let props = {};
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
initializeMockApp({
|
||||
authenticatedUser: {
|
||||
userId: 3,
|
||||
username: 'abc123',
|
||||
administrator: true,
|
||||
roles: [],
|
||||
},
|
||||
});
|
||||
|
||||
props = {
|
||||
intl: {},
|
||||
displayDemographicsLink: false,
|
||||
};
|
||||
store = configureStore({
|
||||
notificationPreferences: {
|
||||
showPreferences: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render Optional Information link', () => {
|
||||
it('should not render Optional Information or delete account link', () => {
|
||||
setConfig({
|
||||
ENABLE_ACCOUNT_DELETION: false,
|
||||
});
|
||||
|
||||
const tree = renderer.create((
|
||||
// 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">
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<IntlJumpNav {...props} />
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render Optional Information link', () => {
|
||||
it('should render Optional Information and delete account link', () => {
|
||||
setConfig({
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION: true,
|
||||
ENABLE_ACCOUNT_DELETION: true,
|
||||
});
|
||||
|
||||
props = {
|
||||
@@ -48,12 +68,11 @@ describe('JumpNav', () => {
|
||||
};
|
||||
|
||||
const tree = renderer.create((
|
||||
// Same as previous test
|
||||
<Router>
|
||||
<IntlProvider locale="en">
|
||||
<IntlProvider locale="en">
|
||||
<AppProvider store={store}>
|
||||
<IntlJumpNav {...props} />
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
</AppProvider>
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
// 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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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-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}
|
||||
>
|
||||
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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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>
|
||||
</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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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-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}
|
||||
>
|
||||
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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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-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}
|
||||
>
|
||||
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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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-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}
|
||||
>
|
||||
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={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"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-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}
|
||||
>
|
||||
Test Field
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
>
|
||||
Default Confirmation Message
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,194 +1,197 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`JumpNav should not render Optional Information link 1`] = `
|
||||
exports[`JumpNav should not render Optional Information or delete account link 1`] = `
|
||||
<div
|
||||
className="jump-nav"
|
||||
data-testid="redux-provider"
|
||||
>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
<div
|
||||
data-testid="browser-router"
|
||||
>
|
||||
<li
|
||||
className=""
|
||||
<div
|
||||
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
onClick={[Function]}
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
>
|
||||
Account Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#profile-information"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Profile Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#social-media"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Social Media Links
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#site-preferences"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Site Preferences
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#linked-accounts"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Linked Accounts
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#delete-account"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Delete My Account
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Account Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#profile-information"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Profile Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#social-media"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Social Media Links
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#site-preferences"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Site Preferences
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#linked-accounts"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Linked Accounts
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`JumpNav should render Optional Information link 1`] = `
|
||||
exports[`JumpNav should render Optional Information and delete account link 1`] = `
|
||||
<div
|
||||
className="jump-nav"
|
||||
data-testid="redux-provider"
|
||||
>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
<div
|
||||
data-testid="browser-router"
|
||||
>
|
||||
<li
|
||||
className=""
|
||||
<div
|
||||
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
onClick={[Function]}
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
>
|
||||
Account Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#profile-information"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Profile Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#demographics-information"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Optional Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#social-media"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Social Media Links
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#site-preferences"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Site Preferences
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#linked-accounts"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Linked Accounts
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#delete-account"
|
||||
onClick={[Function]}
|
||||
style={Object {}}
|
||||
>
|
||||
Delete My Account
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Account Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#profile-information"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Profile Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#demographics-information"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Optional Information
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#social-media"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Social Media Links
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#site-preferences"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Site Preferences
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#linked-accounts"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Linked Accounts
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#delete-account"
|
||||
isActive={[Function]}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Delete My Account
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
112
src/account-settings/test/mockData.js
Normal file
112
src/account-settings/test/mockData.js
Normal file
@@ -0,0 +1,112 @@
|
||||
const mockData = {
|
||||
accountSettings: {
|
||||
loading: false,
|
||||
loaded: true,
|
||||
loadingError: null,
|
||||
data: null,
|
||||
values: {
|
||||
username: 'test_username',
|
||||
country: 'AD',
|
||||
accomplishments_shared: false,
|
||||
name: 'test_name',
|
||||
email: 'test_email@test.com',
|
||||
id: 533,
|
||||
verified_name: null,
|
||||
extended_profile: [
|
||||
{
|
||||
field_name: 'work_experience',
|
||||
field_value: '',
|
||||
},
|
||||
],
|
||||
gender: null,
|
||||
|
||||
'pref-lang': 'en',
|
||||
shouldDisplayDemographicsSection: false,
|
||||
demographicsOptions: false,
|
||||
},
|
||||
errors: {},
|
||||
confirmationValues: {},
|
||||
drafts: {},
|
||||
saveState: null,
|
||||
timeZones: [
|
||||
{
|
||||
time_zone: 'Africa/Abidjan',
|
||||
description: 'Africa/Abidjan (GMT, UTC+0000)',
|
||||
},
|
||||
],
|
||||
countryTimeZones: [
|
||||
{
|
||||
time_zone: 'Europe/Andorra',
|
||||
description: 'Europe/Andorra (CET, UTC+0100)',
|
||||
},
|
||||
],
|
||||
previousSiteLanguage: null,
|
||||
deleteAccount: {
|
||||
status: null,
|
||||
errorType: null,
|
||||
},
|
||||
siteLanguage: {
|
||||
loading: false,
|
||||
loaded: true,
|
||||
loadingError: null,
|
||||
siteLanguageList: [
|
||||
{
|
||||
code: 'en',
|
||||
name: 'English',
|
||||
released: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
resetPassword: {
|
||||
status: null,
|
||||
},
|
||||
nameChange: {
|
||||
saveState: null,
|
||||
errors: {},
|
||||
},
|
||||
thirdPartyAuth: {
|
||||
providers: [
|
||||
{
|
||||
id: 'oa2-google-oauth2',
|
||||
name: 'Google',
|
||||
connected: false,
|
||||
accepts_logins: true,
|
||||
connectUrl: 'http://localhost:18000/auth/login/google-oauth2/?auth_entry=account_settings&next=%2Faccount%2Fsettings',
|
||||
disconnectUrl: 'http://localhost:18000/auth/disconnect/google-oauth2/?',
|
||||
},
|
||||
],
|
||||
disconnectionStatuses: {},
|
||||
errors: {},
|
||||
},
|
||||
verifiedName: null,
|
||||
mostRecentVerifiedName: {},
|
||||
verifiedNameHistory: {
|
||||
use_verified_name_for_certs: false,
|
||||
results: [],
|
||||
},
|
||||
profileDataManager: null,
|
||||
},
|
||||
notificationPreferences: {
|
||||
showPreferences: false,
|
||||
courses: {
|
||||
status: 'success',
|
||||
courses: [],
|
||||
pagination: {
|
||||
count: 0,
|
||||
currentPage: 1,
|
||||
hasMore: false,
|
||||
totalPages: 1,
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
status: 'idle',
|
||||
updatePreferenceStatus: 'idle',
|
||||
selectedCourse: null,
|
||||
preferences: [],
|
||||
apps: [],
|
||||
nonEditable: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default mockData;
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink, StatefulButton } from '@edx/paragon';
|
||||
import { Hyperlink, StatefulButton } from '@openedx/paragon';
|
||||
|
||||
import Alert from '../Alert';
|
||||
import { disconnectAuth } from './data/actions';
|
||||
@@ -16,7 +16,7 @@ class ThirdPartyAuth extends Component {
|
||||
}
|
||||
const disconnectUrl = e.currentTarget.getAttribute('data-disconnect-url');
|
||||
this.props.disconnectAuth(disconnectUrl, providerId);
|
||||
}
|
||||
};
|
||||
|
||||
renderUnconnectedProvider(url, name) {
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-exports
|
||||
export { default } from './ThirdPartyAuth';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
|
||||
4
src/constants.js
Normal file
4
src/constants.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export const IDLE_STATUS = 'idle';
|
||||
export const LOADING_STATUS = 'loading';
|
||||
export const SUCCESS_STATUS = 'success';
|
||||
export const FAILURE_STATUS = 'failure';
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
reducer as accountSettingsReducer,
|
||||
storeName as accountSettingsStoreName,
|
||||
} from '../account-settings';
|
||||
import notificationPreferencesReducer from '../notification-preferences/data/reducers';
|
||||
|
||||
const createRootReducer = () => combineReducers({
|
||||
[accountSettingsStoreName]: accountSettingsReducer,
|
||||
notificationPreferences: notificationPreferencesReducer,
|
||||
});
|
||||
export default createRootReducer;
|
||||
|
||||
21
src/head/Head.jsx
Normal file
21
src/head/Head.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
|
||||
import messages from './messages';
|
||||
|
||||
const Head = ({ intl }) => (
|
||||
<Helmet>
|
||||
<title>
|
||||
{intl.formatMessage(messages['account.page.title'], { siteName: getConfig().SITE_NAME })}
|
||||
</title>
|
||||
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
|
||||
</Helmet>
|
||||
);
|
||||
|
||||
Head.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(Head);
|
||||
17
src/head/Head.test.jsx
Normal file
17
src/head/Head.test.jsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { render } from '@testing-library/react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import Head from './Head';
|
||||
|
||||
describe('Head', () => {
|
||||
const props = {};
|
||||
it('should match render title tag and fivicon with the site configuration values', () => {
|
||||
render(<IntlProvider locale="en"><Head {...props} /></IntlProvider>);
|
||||
const helmet = Helmet.peek();
|
||||
expect(helmet.title).toEqual(`Account | ${getConfig().SITE_NAME}`);
|
||||
expect(helmet.linkTags[0].rel).toEqual('shortcut icon');
|
||||
expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL);
|
||||
});
|
||||
});
|
||||
11
src/head/messages.js
Normal file
11
src/head/messages.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'account.page.title': {
|
||||
id: 'account.page.title',
|
||||
defaultMessage: 'Account | {siteName}',
|
||||
description: 'Title tag',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
59
src/hooks.js
59
src/hooks.js
@@ -1,23 +1,68 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { logError } from '@edx/frontend-platform/logging';
|
||||
|
||||
import {
|
||||
IDLE_STATUS, LOADING_STATUS, SUCCESS_STATUS, FAILURE_STATUS,
|
||||
} from './constants';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function useAsyncCall(asyncFunc) {
|
||||
const [isLoading, setIsLoading] = useState();
|
||||
const [data, setData] = useState();
|
||||
// React doesn't batch setStates call in async useEffect hooks,
|
||||
// so we use a combined object here to ensure that users
|
||||
// re-render once.
|
||||
const [data, setData] = useState({ status: IDLE_STATUS });
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
setData(currData => ({ ...currData, status: LOADING_STATUS }));
|
||||
const response = await asyncFunc();
|
||||
setIsLoading(false);
|
||||
if (response) {
|
||||
setData(response);
|
||||
|
||||
if (Object.keys(response).length === 0) {
|
||||
setData(currData => ({ ...currData, status: FAILURE_STATUS, data: response }));
|
||||
} else {
|
||||
setData(currData => ({ ...currData, status: SUCCESS_STATUS, data: response }));
|
||||
}
|
||||
})();
|
||||
},
|
||||
[asyncFunc],
|
||||
);
|
||||
|
||||
return [isLoading, data];
|
||||
return data;
|
||||
}
|
||||
|
||||
// Redirect the user to their original location based on session storage
|
||||
export function useRedirect() {
|
||||
const [redirect, setRedirect] = useState({
|
||||
location: 'dashboard',
|
||||
text: 'id.verification.return.dashboard',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionStorage.getItem('courseId')) {
|
||||
setRedirect({
|
||||
location: `courses/${sessionStorage.getItem('courseId')}`,
|
||||
text: 'id.verification.return.course',
|
||||
});
|
||||
} else if (sessionStorage.getItem('next')) {
|
||||
setRedirect({
|
||||
location: sessionStorage.getItem('next'),
|
||||
text: 'id.verification.return.generic',
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
return redirect;
|
||||
}
|
||||
|
||||
export function useFeedbackWrapper() {
|
||||
useEffect(() => {
|
||||
try {
|
||||
// eslint-disable-next-line no-undef
|
||||
window.usabilla_live = lightningjs?.require('usabilla_live', getConfig().LEARNER_FEEDBACK_URL);
|
||||
} catch (error) {
|
||||
logError('Error loading usabilla_live', error);
|
||||
}
|
||||
}, []);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
import { messages as headerMessages } from '@edx/frontend-component-header';
|
||||
import { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
import { messages as paragonMessages } from '@openedx/paragon';
|
||||
import arMessages from './messages/ar.json';
|
||||
import caMessages from './messages/ca.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
import deMessages from './messages/de.json';
|
||||
import es419Messages from './messages/es_419.json';
|
||||
import faIRMessages from './messages/fa_IR.json';
|
||||
import frMessages from './messages/fr.json';
|
||||
import zhcnMessages from './messages/zh_CN.json';
|
||||
import heMessages from './messages/he.json';
|
||||
import idMessages from './messages/id.json';
|
||||
import kokrMessages from './messages/ko_kr.json';
|
||||
import plMessages from './messages/pl.json';
|
||||
import ptbrMessages from './messages/pt_br.json';
|
||||
import frCAMessages from './messages/fr_CA.json';
|
||||
import hiMessages from './messages/hi.json';
|
||||
import itMessages from './messages/it.json';
|
||||
import ptMessages from './messages/pt.json';
|
||||
import ruMessages from './messages/ru.json';
|
||||
import thMessages from './messages/th.json';
|
||||
import ukMessages from './messages/uk.json';
|
||||
import zhcnMessages from './messages/zh_CN.json';
|
||||
import dedeCAMessages from './messages/de_DE.json';
|
||||
import ititCAMessages from './messages/it_IT.json';
|
||||
import ptptCAMessages from './messages/pt_PT.json';
|
||||
// no need to import en messages-- they are in the defaultMessage field
|
||||
|
||||
const messages = {
|
||||
const appMessages = {
|
||||
ar: arMessages,
|
||||
'es-419': es419Messages,
|
||||
'fa-ir': faIRMessages,
|
||||
fr: frMessages,
|
||||
'zh-cn': zhcnMessages,
|
||||
ca: caMessages,
|
||||
he: heMessages,
|
||||
id: idMessages,
|
||||
'ko-kr': kokrMessages,
|
||||
pl: plMessages,
|
||||
'pt-br': ptbrMessages,
|
||||
pt: ptMessages,
|
||||
it: itMessages,
|
||||
de: deMessages,
|
||||
hi: hiMessages,
|
||||
'fr-ca': frCAMessages,
|
||||
ru: ruMessages,
|
||||
th: thMessages,
|
||||
uk: ukMessages,
|
||||
'de-de': dedeCAMessages,
|
||||
'it-it': ititCAMessages,
|
||||
'pt-pt': ptptCAMessages,
|
||||
};
|
||||
|
||||
export default messages;
|
||||
export default [
|
||||
headerMessages,
|
||||
paragonMessages,
|
||||
footerMessages,
|
||||
appMessages,
|
||||
];
|
||||
|
||||
@@ -1,74 +1,90 @@
|
||||
{
|
||||
"account.settings.message.duplicate.tpa.provider": "The {provider} account you selected is already linked to another {siteName} account.",
|
||||
"account.settings.message.managed.settings": "تتم إدارة إعدادات الملف الشخصي بواسطة {ManagerTitle}. اتصل بالمسؤول أو {support} للحصول على المساعدة.",
|
||||
"account.settings.message.duplicate.tpa.provider": "حساب {provider} الذي حددته موصول من قبل بحساب آخر على {siteName}.",
|
||||
"account.settings.message.managed.settings": "إعدادات ملفك الشخصي يديرها {ManagerTitle}. اتصل بمديرك أو ب{support} للحصول على المساعدة.",
|
||||
"account.settings.message.managed.settings.support": "الدعم",
|
||||
"account.settings.page.heading": "إعدادات الحساب",
|
||||
"account.settings.loading.message": "جاري التحميل...",
|
||||
"account.settings.loading.message": "التحميل جارٍ...",
|
||||
"account.settings.loading.error": "خطأ: {error}",
|
||||
"account.settings.banner.beta.language": "لقد قمت بتعيين لغتك الى {beta_language}، والتي لم تتم ترجمتها بالكامل حاليًا. يمكنك مساعدتنا في ترجمة هذه اللغة بالكامل من خلال الانضمام إلى مجتمع Transifex وإضافة ترجمات من الإنجليزية للمتعلمين الذين يتحدثون {beta_language}.",
|
||||
"account.settings.banner.beta.language.action.switch.back": "العودة لـِ {previous_language}",
|
||||
"account.settings.banner.beta.language.action.help.translate": "المساعدة في الترجمة إلى {beta_language}",
|
||||
"account.settings.banner.beta.language": "لقد قمت بضبط لغتك على {beta_language}، وهي حاليا غير مترجمة بالكامل. يمكنك مساعدتنا في إتمام ترجمة هذه اللغة من خلال الانضمام إلى مجتمع Transifex وإضافة ترجمات من الإنجليزية للمتعلمين الذين يتحدثون {beta_language}.",
|
||||
"account.settings.banner.beta.language.action.switch.back": "العودة إلى {previous_language}",
|
||||
"account.settings.banner.beta.language.action.help.translate": "ساعد في الترجمة إلى {beta_language}",
|
||||
"account.settings.section.account.information": "معلومات الحساب",
|
||||
"account.settings.section.account.information.description": "تتضمن هذه الإعدادات معلومات أساسية عن حسابك.",
|
||||
"account.settings.section.profile.information": "معلومات الملف الشخصي",
|
||||
"account.settings.section.demographics.information": "معلومات اختيارية",
|
||||
"account.settings.section.site.preferences": "تفضيلات الموقع",
|
||||
"account.settings.section.linked.accounts": "الحسابات المرتبطة",
|
||||
"account.settings.section.linked.accounts.description": "You can link your identity accounts to simplify signing in to {siteName}.",
|
||||
"account.settings.section.linked.accounts": "الحسابات الموصولة",
|
||||
"account.settings.section.linked.accounts.description": "يمكنك وصل حسابات هويتك لتبسيط تسجيل دخولك إلى {siteName}.",
|
||||
"account.settings.field.username": "اسم المستخدم",
|
||||
"account.settings.field.username.help.text": "The name that identifies you on {siteName}. You cannot change your username.",
|
||||
"account.settings.field.username.help.text": "الاسم الذي يعرّفك على {siteName}. لا يمكنك تغيير اسم المستخدم الخاص بك.",
|
||||
"account.settings.field.full.name": "الاسم الكامل",
|
||||
"account.settings.field.full.name.empty": "إضافة اسم",
|
||||
"account.settings.field.full.name.help.text": "الاسم المستخدم للتحقق من هويتك والذي سوف يظهر على الشهادات الخاصة بك.",
|
||||
"account.settings.field.full.name.help.text.non.certificate": "The name that appears on your public profile.",
|
||||
"account.settings.field.full.name.help.text.certificate": "This name is selected to appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.verified": "Verified name",
|
||||
"account.settings.field.name.verified.help.text.verified": "This name has been verified by government ID.",
|
||||
"account.settings.field.name.verified.help.text.certificate": "This name has been verified by government ID and selected to appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.verified.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Verified name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your government-issued ID.",
|
||||
"account.settings.field.full.name.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.",
|
||||
"account.settings.field.full.name.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.success.message": "Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.",
|
||||
"account.settings.field.name.verified.success.message.header": "Your name change request is complete!",
|
||||
"account.settings.field.name.verified.failure.message": "Your most recent identity verification attempt did not pass. Related account settings have been restored.",
|
||||
"account.settings.field.name.verified.failure.message.header": "We were not able to verify your identity.",
|
||||
"account.settings.field.name.verified.failure.message.help.link": "Learn more about ID verification",
|
||||
"account.settings.field.name.verified.submitted.message": "Your identity verification request has been submitted and usually takes between 24 and 48 hours to complete.",
|
||||
"account.settings.field.name.verified.submitted.message.certificate": "When your request is approved, your updated name will appear on all associated certificates and public-facing records.",
|
||||
"account.settings.field.name.verified.submitted.message.header": "Your name change request is almost complete!",
|
||||
"account.settings.field.email": "البريد الالكتروني (الدخول)",
|
||||
"account.settings.field.full.name.empty": "إضافة الاسم",
|
||||
"account.settings.field.full.name.help.text": "الاسم المستعمل للتحقق من هويتك والذي يظهر في شهاداتك.",
|
||||
"account.settings.field.full.name.help.text.default": "الاسم الذي يظهر في ملفك الشخصي العامّ.",
|
||||
"account.settings.field.full.name.help.text.default.certificate": "سيظهر هذا الاسم في شهاداتك و سجلّاتك العامّة.",
|
||||
"account.settings.field.name.verified": "اسم متحقَّق منه",
|
||||
"account.settings.field.name.verified.help.text.verified": "تم التحقق من هذا الاسم عن طريق بطاقة تعريف ذات صورة.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored": "تم التحقق من هذا الاسم عن طريق المراقبة.",
|
||||
"account.settings.field.name.verified.help.text.verified.certificate": "تم التحقق من هذا الاسم عن طريق بطاقة هوية ذات صورة، وتم اختياره للظهور في الشهادات والسجلات العامة.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "تم التحقق من هذا الاسم من خلال المراقبة، وتم تحديده للظهور في الشهادات والسجلات العامة.",
|
||||
"account.settings.field.name.verified.help.text.submitted": "تم تسليم طلب التحقق. في العادة يستغرق هذا 48 ساعة أو أقل. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored": "تم تسليم امتحانك المراقَب. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت. رجاءً أعد التفقد بعد 2-5 أيام.",
|
||||
"account.settings.field.name.verified.help.text.submitted.certificate": "عند نجاح التحقق من الهوية، سيظهر هذا الاسم على شهاداتك و سجلاتك العامة. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "بمجرد اجتياز امتحانك المراقب للمراجعة، سيظهر هذا الاسم في شهادتك و سجلاتك العامة. لا يمكن تغيير الاسم المتحقَّق منه في هذا الوقت.",
|
||||
"account.settings.field.name.verified.verification.help": "أدخل اسمك كما يظهر في بطاقة تعريف الطالب أو الموظف الخاصة بك، أو بطاقة تعريفك الصادرة عن الحكومة.",
|
||||
"account.settings.field.full.name.help.text.submitted": "تم تسليم طلب التحقق. يستغرق هذا عادةً 48 ساعة أو أقل. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored": "تم تسليم امتحانك المراقب. لا يمكن تغيير الاسم الكامل في هذا الوقت. يرجى التحقق مرة أخرى خلال 2-5 أيام.",
|
||||
"account.settings.field.full.name.help.text.submitted.certificate": "عند نجاح التحقق من الهوية، سيظهر هذا الاسم على الشهادات والسجلات العامة. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "بمجرد اجتياز امتحانك المراقب للمراجعة، سيظهر هذا الاسم في شهاداتك وسجلاتك العامة. لا يمكن تغيير الاسم الكامل في هذا الوقت.",
|
||||
"account.settings.field.name.verified.success.message": "اكتمل طلب التحقق من هويتك بنجاح. لديك الآن خيار تحديد الاسم الذي تفضل ظهوره على شهاداتك وسجلاتك العامة.",
|
||||
"account.settings.field.name.verified.success.message.header": "اكتمل طلب تغيير اسمك!",
|
||||
"account.settings.field.name.verified.failure.message": "لم تنجح آخر محاولاتك للتحقق من هويتك. تم إرجاع إعدادات الحساب ذات الصلة إلى وضعها السابق.",
|
||||
"account.settings.field.name.verified.failure.message.header": "لم نستطع التحقق من هويتك.",
|
||||
"account.settings.field.name.verified.failure.message.help.link": "اعرف المزيد عن التحقق من الهوية.",
|
||||
"account.settings.field.name.verified.submitted.message": "تم تسليم طلب التحقق من هويتك و عادة ما يستغرق إكماله ما بين 24 و 48 ساعة.",
|
||||
"account.settings.field.name.verified.submitted.message.certificate": "عند الموافقة على طلبك، سيظهر اسمك المحدَّث على جميع الشهادات السجلات العامة ذات الصلة.",
|
||||
"account.settings.field.name.verified.submitted.message.header": "طلب تغيير اسمك يكاد يكتمل!",
|
||||
"account.settings.field.email": "البريد الالكتروني (تسجيل الدخول)",
|
||||
"account.settings.field.email.empty": "إضافة عنوان البريد الإلكتروني",
|
||||
"account.settings.field.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريدك الإلكتروني.",
|
||||
"account.settings.field.email.help.text": "You receive messages from {siteName} and course teams at this address.",
|
||||
"account.settings.field.secondary.email": "عنوان البريد الإلكتروني للاسترداد",
|
||||
"account.settings.field.secondary.email.empty": "إضافة عنوان البريد الإلكتروني للاسترداد",
|
||||
"account.settings.field.secondary.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر فوق الرابط في الرسالة لتحديث عنوان بريد الاسترداد الإلكتروني.",
|
||||
"account.settings.field.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر على الرابط في الرسالة لتحديث عنوان بريدك الإلكتروني.",
|
||||
"account.settings.field.email.help.text": "على هذا العنوان تصلك الرسائل من {siteName} و من فرق المساقات.",
|
||||
"account.settings.field.secondary.email": "عنوان بريد الاستعادة الإلكتروني.",
|
||||
"account.settings.field.secondary.email.empty": "إضافة عنوان بريد إلكتروني لاستعادة حسابك",
|
||||
"account.settings.field.secondary.email.confirmation": "لقد أرسلنا رسالة تأكيد إلى {value}. انقر على الرابط في الرسالة لتحديث عنوان بريد الاستعادة الإلكتروني الخاص بك.",
|
||||
"account.settings.email.field.confirmation.header": "تعليق عملية التأكيد",
|
||||
"account.settings.field.dob": "سنة الميلاد",
|
||||
"account.settings.field.dob.empty": "إضافة سنة الميلاد",
|
||||
"account.settings.field.year_of_birth.options.empty": "اختر سنة ميلاد",
|
||||
"account.settings.field.country": "الدولة",
|
||||
"account.settings.field.year_of_birth.options.empty": "تحديد سنة الميلاد",
|
||||
"account.settings.field.dob.month": "الشهر",
|
||||
"account.settings.field.dob.year": "السنة",
|
||||
"account.settings.field.month.year.default": "اختر الشهر",
|
||||
"account.settings.field.dob.year.default": "اختر السنة",
|
||||
"account.settings.field.dob.form.button": "يرجى تأكيد تاريخ ميلادك",
|
||||
"account.settings.field.dob.form.title": "أدخل شهر وسنة ميلادك",
|
||||
"account.settings.field.dob.form.help.text": "نطلب معلومات شهر وسنة الميلاد لمساعدتنا على الامتثال لالتزاماتنا القانونية.",
|
||||
"account.settings.field.dob.form.success": "شكرا لك على إدخال المعلومات الخاصة بك.",
|
||||
"account.settings.field.month_of_birth.options.empty": "تحديد شهر الميلاد",
|
||||
"account.settingsfield.dob.error.general": "حدث خطأ تقني. رجاءً حاول مجددا.",
|
||||
"account.settings.field.country": "البلد",
|
||||
"account.settings.field.country.empty": "إضافة البلد ",
|
||||
"account.settings.field.country.options.empty": "اختر دولة",
|
||||
"account.settings.field.state": "الحالة",
|
||||
"account.settings.field.state.empty": "إضافة منطقة",
|
||||
"account.settings.field.state.options.empty": "اختر منطقة",
|
||||
"account.settings.field.country.options.empty": "اختر البلد",
|
||||
"account.settings.field.state": "المنطقة / الولاية / المحافظة",
|
||||
"account.settings.field.state.empty": "إضافة المنطقة / الولاية / المحافظة",
|
||||
"account.settings.field.state.options.empty": "حدد المنطقة",
|
||||
"account.settings.field.site.language": "لغة الموقع",
|
||||
"account.settings.field.site.language.help.text": "اللغة المستخدمة في كافة أقسام هذا الموقع. يتوفّر هذا الموقع حاليًا بعدد محدود من اللغات.",
|
||||
"account.settings.field.education": "المستوى التعليمي",
|
||||
"account.settings.field.education.empty": "إضافة المستوى التعليمي",
|
||||
"account.settings.field.education.levels.empty": "اختر المستوى التعليمي",
|
||||
"account.settings.field.education.levels.empty": "اختر مستوى تعليميًّا",
|
||||
"account.settings.field.education.levels.p": "دكتوراه",
|
||||
"account.settings.field.education.levels.m": "ماجستير أو شهادة مهنيّة",
|
||||
"account.settings.field.education.levels.b": "بكالوريوس",
|
||||
"account.settings.field.education.levels.a": "زمالة",
|
||||
"account.settings.field.education.levels.hs": "شهادة الثانوية العامة",
|
||||
"account.settings.field.education.levels.jhs": "شهادة الثانوية الصغرى/الإعدادية/المرحلة المتوسّطة",
|
||||
"account.settings.field.education.levels.el": "شهادة المدرسة الابتدائية",
|
||||
"account.settings.field.education.levels.none": "لا يوجد تعليم رسمي",
|
||||
"account.settings.field.education.levels.o": "نوع آخر من التعليم",
|
||||
"account.settings.field.education.levels.m": "ماجستير / ماستر أو شهادة مهنيّة",
|
||||
"account.settings.field.education.levels.b": "بكالوريوس / ليسانس",
|
||||
"account.settings.field.education.levels.a": "درجة الزمالة / دبلوم الدراسات الجامعية",
|
||||
"account.settings.field.education.levels.hs": "الثانوية العامة / البكالوريا",
|
||||
"account.settings.field.education.levels.jhs": "المدرسة الإعدادية / المتوسطة",
|
||||
"account.settings.field.education.levels.el": "المدرسة الابتدائية / الأساسية",
|
||||
"account.settings.field.education.levels.none": "دون تعليم رسمي",
|
||||
"account.settings.field.education.levels.other": "Other education",
|
||||
"account.settings.field.gender": "الجنس",
|
||||
"account.settings.field.gender.empty": "إضافة الجنس",
|
||||
"account.settings.field.gender.options.empty": "اختر جنسًا",
|
||||
@@ -80,183 +96,84 @@
|
||||
"account.settings.field.language_proficiencies.options.empty": "اختر لغة",
|
||||
"account.settings.field.time.zone": "المنطقة الزمنية",
|
||||
"account.settings.field.time.zone.empty": "ضبط المنطقة الزمنية",
|
||||
"account.settings.field.time.zone.description": "حدد المنطقة الزمنية لعرض تواريخ المساق. إذا لم تحدد منطقة زمنية، فسيتم عرض تواريخ المساق، بما في ذلك المواعيد النهائية للواجب في المنطقة الزمنية المحلية في المتصفح.",
|
||||
"account.settings.field.time.zone.default": "الافتراضي (منطقة التوقيت المحلي)",
|
||||
"account.settings.field.time.zone.description": "حدد المنطقة الزمنية لعرض تواريخ المساقات. إذا لم تحدد منطقة زمنية، فسيتم عرض تواريخ المساق، بما في ذلك المواعيد النهائية للواجبات بالتوقيت المحلّي للمتصفح.",
|
||||
"account.settings.field.time.zone.default": "الافتراضي (المنطقة الزمنية المحلية)",
|
||||
"account.settings.field.time.zone.all": "جميع المناطق الزمنية",
|
||||
"account.settings.field.time.zone.country": "المنطقة الزمنية للدولة",
|
||||
"account.settings.section.social.media": "روابط منصات التواصل الإجتماعي",
|
||||
"account.settings.section.social.media.description": "Optionally, link your personal accounts to the social media icons on your {siteName} profile.",
|
||||
"account.settings.field.time.zone.country": "المناطق الزمنية للبلدان",
|
||||
"account.settings.section.social.media": "روابط التواصل الإجتماعي",
|
||||
"account.settings.section.social.media.description": "اختياريًا، اربط حساباتك الشخصية بأيقونات التواصل الاجتماعي في ملفك على {siteName}.",
|
||||
"account.settings.field.social.platform.name.linkedin": "لينكد إن",
|
||||
"account.settings.field.social.platform.name.linkedin.empty": "إضافة عنوان ملف لينكد إن ",
|
||||
"account.settings.jump.nav.delete.account": "احذف حسابي",
|
||||
"account.settings.field.social.platform.name.linkedin.empty": "إضافة سيرة لينكد إن",
|
||||
"account.settings.jump.nav.delete.account": "حذف حسابي",
|
||||
"account.settings.field.social.platform.name.twitter": "تويتر",
|
||||
"account.settings.field.social.platform.name.twitter.empty": "إضافة عنوان صفحة تويتر",
|
||||
"account.settings.field.social.platform.name.twitter.empty": "إضافة صفحة تويتر",
|
||||
"account.settings.field.social.platform.name.facebook": "فيسبوك",
|
||||
"account.settings.field.social.platform.name.facebook.empty": "إضافة عنوان صفحة فيسبوك",
|
||||
"account.settings.field.social.platform.name.facebook.empty": "إضافة حساب فيسبوك",
|
||||
"account.settings.editable.field.action.save": "حفظ",
|
||||
"account.settings.editable.field.action.cancel": "إلغاء",
|
||||
"account.settings.editable.field.action.edit": "تحرير",
|
||||
"account.settings.static.field.empty": "لم يتم تحديد قيمة، فضلًا اتصل بمدير {enterprise} لتعيين بعض التغييرات.",
|
||||
"account.settings.static.field.empty.no.admin": "لم يتم تحديد قيمة",
|
||||
"account.settings.field.name.certificate.select": "If checked, this name will appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.modal.certificate.title": "Choose a preferred name for certificates and public-facing records",
|
||||
"account.settings.field.name.modal.certificate.select": "Select a name",
|
||||
"account.settings.field.name.modal.certificate.option.full": "Full Name",
|
||||
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
|
||||
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
|
||||
"account.settings.coaching.consent.welcome.header": "لنبدأ",
|
||||
"account.settings.coaching.consent.welcome.subheader": "نحن هنا لأجلك من البداية حتى النهاية",
|
||||
"account.settings.coaching.consent.description": "تتضمن برامج البكالوريوس التدريب الذي يركز على مهنتك وتعليمك وكيفية تحقيق نتائج مبهرة من خلال التواصل الشخصي مع خبراء متمرسين. إذا كنت مهتمًا، فقدّم المعلومات أدناه وانقر فوق \"إرسال\"، وسيتصل بك شريكنا في التدريب عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
|
||||
"account.settings.coaching.consent.text-messaging.disclaimer": "* يتم تضمين خدمات التدريب بدون أي تكلفة إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. يتضمن التدريب رسائل نصية متكررة. قد تنطبق أسعار الرسائل والبيانات. إيقاف النص لإلغاء الاشتراك.",
|
||||
"account.settings.coaching.consent.accept-coaching": "سجّل للاستفادة من خدمات التدريب",
|
||||
"account.settings.coaching.consent.decline-coaching": "أفضّل عدم الاتصال بخدمات التدريب المجانية",
|
||||
"account.settings.coaching.consent.label.name": "يرجى تأكيد الاسم",
|
||||
"account.settings.coaching.consent.label.phone-number": "فضلًا أدخل رقم الهاتف ",
|
||||
"account.settings.coaching.consent.success.header": "تمت العملية بنجاح",
|
||||
"account.settings.coaching.consent.success.message": "لقد اشتركت في التدريب. يمكنك توقع استلام رسالة عبر البريد الإلكتروني أو الرسائل القصيرة في الأيام المقبلة.",
|
||||
"account.settings.coaching.consent.success.continue": "البدء في مساقي",
|
||||
"account.settings.coaching.managed.support": "الدعم",
|
||||
"account.settings.coaching.managed.alert": "تتم إدارة اسمك بواسطة {ManagerTitle}. اتصل بالمسؤول للحصول على المساعدة.",
|
||||
"account.settings.field.phone_number": "رقم الهاتف",
|
||||
"account.settings.field.phone_number.empty": "إضافة رقم الهاتف",
|
||||
"account.settings.field.coaching_consent": "اتفاقية التدريب",
|
||||
"account.settings.field.coaching_consent.tooltip": "تتضمن برامج البكالوريوس التدريب القائم على الرسائل النصية الذي يساعدك على إقران التجارب التعليمية مع أهدافك المهنية من خلال النصائح الشخصية. يتم تضمين خدمات التدريب بدون أي تكلفة إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف نقالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل \"قف\" في أي وقت لإلغاء الرسائل.",
|
||||
"account.settings.field.coaching_consent.error": "مطلوب رقم هاتف أمريكي صالح للدخول في التدريب",
|
||||
"account.settings.delete.account.before.proceeding": "قبل المتابعة، يرجى {actionLink}.",
|
||||
"account.settings.delete.account.header": "احذف حسابي",
|
||||
"account.settings.delete.account.subheader": "نأسف لذهابك!",
|
||||
"account.settings.delete.account.text.1": "Please note: Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
|
||||
"account.settings.delete.account.text.2": "Once your account is deleted, you cannot use it to take courses on {siteName}.",
|
||||
"account.settings.delete.account.text.2.edX": "Once your account is deleted, you cannot use it to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer’s or university’s system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
|
||||
"account.settings.delete.account.text.3.link": "Follow these instructions for printing or downloading a certificate",
|
||||
"account.settings.delete.account.text.warning": "Warning: Account deletion is permanent. Please read the above carefully before proceeding. This is an irreversible action, and you will no longer be able to use the same email on {siteName}.",
|
||||
"account.settings.delete.account.text.change.instead": "هل تريد تغيير البريد الإلكتروني أو الاسم أو كلمة المرور بدلاً من ذلك؟",
|
||||
"account.settings.delete.account.button": "احذف حسابي",
|
||||
"account.settings.delete.account.please.activate": "تنشيط حسابك",
|
||||
"account.settings.delete.account.please.unlink": "إلغاء ربط جميع حسابات التواصل الاجتماعي",
|
||||
"account.settings.delete.account.modal.header": "هل أنت متأكد؟",
|
||||
"account.settings.delete.account.modal.text.1": "You have selected \"Delete My Account\". Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
|
||||
"account.settings.delete.account.modal.text.2": "If you proceed, you will be unable to use this account to take courses on {siteName}.",
|
||||
"account.settings.delete.account.modal.text.2.edX": "If you proceed, you will be unable to use this account to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer's or university's system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
|
||||
"account.settings.delete.account.modal.enter.password": "إذا كنت لا تزال ترغب في المتابعة وحذف حسابك ، فيرجى إدخال كلمة مرور حسابك:",
|
||||
"account.settings.delete.account.modal.confirm.delete": "تعم، أحذف",
|
||||
"account.settings.delete.account.modal.confirm.cancel": "إلغاء",
|
||||
"account.settings.delete.account.error.unable.to.delete": "تعذر حذف الحساب",
|
||||
"account.settings.delete.account.error.no.password": "كلمة المرور مطلوبة",
|
||||
"account.settings.delete.account.error.invalid.password": "كلمة المرور المدخلة غير صحيحة",
|
||||
"account.settings.delete.account.error.unable.to.delete.details": "عذراً ، حدث خطأ أثناء محاولة معالجة طلبك. الرجاء معاودة المحاولة في وقت لاحق.",
|
||||
"account.settings.delete.account.modal.after.header": "نأسف لذهابك! سيتم حذف حسابك قريبا.",
|
||||
"account.settings.delete.account.modal.after.text": "قد يستغرق حذف الحساب ، بما في ذلك الإزالة من قوائم البريد الإلكتروني ، بضعة أسابيع حتى تتم معالجته بالكامل من خلال نظامنا. إذا كنت ترغب في إلغاء الاشتراك في رسائل البريد الإلكتروني قبل ذلك الحين ، يرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
|
||||
"account.settings.delete.account.modal.after.button": "إغلاق ",
|
||||
"account.settings.delete.account.text.3.edX": "You may also lose access to verified certificates and other program credentials like MicroMasters certificates. You can make a copy of these for your records before proceeding with deletion. {actionLink}.",
|
||||
"account.settings.delete.account.text.3": "You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.",
|
||||
"account.settings.message.demographics.service.issue": "حدث خطأ أثناء محاولة استرداد معلومات حسابك أو حفظها. يرجى المحاولة مرة أخرى لاحقًا.",
|
||||
"account.settings.field.demographics.gender": "هوية الجنس",
|
||||
"account.settings.field.demographics.gender.empty": "إضافة هوية الجنس",
|
||||
"account.settings.field.demographics.gender.options.empty": "حدد هوية الجنس",
|
||||
"account.settings.field.demographics.gender_description": "وصف هوية الجنس",
|
||||
"account.settings.field.demographics.gender_description.empty": "أدخل وصفًا",
|
||||
"account.settings.field.demographics.ethnicity": "هوية العرق/ الأصل",
|
||||
"account.settings.field.demographics.ethnicity.empty": "إضافة هوية العرق/الأصل",
|
||||
"account.settings.field.demographics.ethnicity.options.empty": "فضلًا اختر جميع ما ينطبق",
|
||||
"account.settings.field.demographics.income": "الدخل المادي الأسري",
|
||||
"account.settings.field.demographics.income.empty": "إضافة الدخل المادي الأسري",
|
||||
"account.settings.field.demographics.income.options.empty": "حدد نطاق الدخل المادي للأسرة",
|
||||
"account.settings.field.demographics.military_history": "حالة الخدمة العسكرية في الولايات المتحدة الأمريكية",
|
||||
"account.settings.field.demographics.military_history.empty": "إضافة حالة الخدمة العسكرية",
|
||||
"account.settings.field.demographics.military_history.options.empty": "اختر حالة الخدمة العسكرية",
|
||||
"account.settings.field.demographics.learner_education_level": "مؤهلك التعليمي",
|
||||
"account.settings.field.demographics.learner_education_level.empty": "إضافة مؤهلك التعليمي",
|
||||
"account.settings.field.demographics.parent_education_level": "مؤهل الوالدين/الآوصياء التعليمي",
|
||||
"account.settings.field.demographics.parent_education_level.empty": "إضافة المؤهل التعليمي",
|
||||
"account.settings.field.demographics.education_level.options.empty": "حدد موهلاً تعليميًا",
|
||||
"account.settings.field.demographics.work_status": "الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status.empty": "إضافة الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status.options.empty": "فضلًا حدد حالتك الوظيفية",
|
||||
"account.settings.field.demographics.work_status_description": "وصف الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status_description.empty": "أدخل وصفًا",
|
||||
"account.settings.field.demographics.current_work_sector": "مجال العمل الحالي",
|
||||
"account.settings.field.demographics.current_work_sector.empty": "إضافة مجال العمل",
|
||||
"account.settings.field.demographics.future_work_sector": "مجال العمل المستقبلي",
|
||||
"account.settings.field.demographics.future_work_sector.empty": "إضافة مجال العمل",
|
||||
"account.settings.field.demographics.work_sector.options.empty": "حدد مجال العمل",
|
||||
"account.settings.section.demographics.why": "Why does {siteName} collect this information?",
|
||||
"account.settings.name.change.title.id": "This name change requires identity verification",
|
||||
"account.settings.name.change.title.begin": "Before we begin",
|
||||
"account.settings.name.change.warning.one": "Warning: This action updates the name that appears on all certificates that have been earned on this account in the past and any certificates you are currently earning or will earn in the future.",
|
||||
"account.settings.name.change.warning.two": "This action cannot be undone without verifying your identity.",
|
||||
"account.settings.name.change.id.name.label": "Enter your name as it appears on your government-issued ID.",
|
||||
"account.settings.name.change.id.name.placeholder": "Enter the name on your government ID",
|
||||
"account.settings.name.change.error.valid.name": "Please enter a valid name.",
|
||||
"account.settings.name.change.error.general": "A technical error occurred. Please try again.",
|
||||
"account.settings.name.change.continue": "Continue",
|
||||
"account.settings.name.change.cancel": "Cancel",
|
||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في نص الرابط. الرجاء التحقق من الرابط والمحاولة مجددا.",
|
||||
"account.settings.editable.field.password.reset.button.confirmation.support.link": "الدعم الفني",
|
||||
"account.settings.editable.field.password.reset.button.confirmation": "لقد أرسلنا رسالة إلى {email}. انقر فوق الرابط في الرسالة لإعادة تعيين كلمة المرور. إذا لم يتم استلام الرسالة؟ اتصل بـ {technicalSupportLink}.",
|
||||
"account.settings.editable.field.password.reset.button": "تغيير كلمة المرور",
|
||||
"account.settings.editable.field.password.reset.button.forbidden": "طلبك السابق قيد التقدم، يرجى إعادة المحاولة بعد لحظات قليلة.",
|
||||
"account.settings.editable.field.password.reset.label": "كلمة المرور",
|
||||
"account.settings.sso.link.account": "تسجيل الدخول كـ {name}",
|
||||
"account.settings.sso.account.connected": "مربوط",
|
||||
"account.settings.sso.account.disconnect.error": "حدثت مشكلة أثناء قطع اتصال هذا الحساب، اتصل بالدعم عند استمرار المشكلة.",
|
||||
"account.settings.sso.unlink.account": "إلغاء ربط حساب {name} ",
|
||||
"account.settings.sso.no.providers": "لا يمكن ربط أية حسابات حاليًا",
|
||||
"id.verification.access.blocked.denied": "لا يمكنك التحقق من هويتك في الوقت الحالي. إذا لم تقم بعد بتنشيط حسابك، فيرجى التحقق من مجلد البريد المهمل للحصول على رسالة التفعيل من {email}.",
|
||||
"account.settings.editable.field.action.edit": "تعديل",
|
||||
"account.settings.static.field.empty": "لا قيمة محددة. رجاءً اتصل بمديرك في {enterprise} ليقوم بالتعديلات.",
|
||||
"account.settings.static.field.empty.no.admin": "لا قيمة محددة.",
|
||||
"notification.preferences.notifications.label": "Notifications",
|
||||
"account.settings.work.experience": "Work Experience",
|
||||
"account.settings.field.work.experience.empty": "Add work experience",
|
||||
"account.settings.field.work.experience.options.empty": "Select work experience",
|
||||
"error.notfound.message": "الصفحة التي تبحث عنها غير متوفرة أو هناك خطأ في العنوان. رجاءً تحقق من العنوان و حاول مجددًا.",
|
||||
"account.page.title": "الحساب | {siteName}",
|
||||
"id.verification.access.blocked.denied": "لا يمكننا التحقق من هويتك في الوقت الراهن. إن لم تكن قد فعّلت حسابك بعد، فيرجى تفقد مجلد الرسائل غير المرغوب فيها بحثًا عن بريد التفعيل الإلكتروني من {email}.",
|
||||
"id.verification.next": "التالي",
|
||||
"id.verification.support": "support",
|
||||
"id.verification.continue.upload": "Continue with Upload",
|
||||
"id.verification.example.card.alt": "مثال على بطاقة هوية صحيحة بالاسم الكامل وصورة.",
|
||||
"id.verification.requirements.title": "متطلبات التحقق من الصورة",
|
||||
"id.verification.requirements.description": "In order to complete Photo Verification, you will need the following:",
|
||||
"id.verification.support": "الدعم",
|
||||
"id.verification.example.card.alt": "مثال بطاقة تعريف صحيحة بالاسم الكامل والصورة.",
|
||||
"id.verification.requirements.title": "متطلبات التحقق باستخدام الصورة",
|
||||
"id.verification.requirements.description": "لإكمال التحقق بالصورة، ستحتاج ما يلي:",
|
||||
"id.verification.requirements.card.device.title": "جهاز مزود بكاميرا",
|
||||
"id.verification.requirements.card.device.allow": "موافق",
|
||||
"id.verification.requirements.card.id.title": "Photo Identification Card",
|
||||
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo, such as a driver’s license or passport.",
|
||||
"id.verification.privacy.title": "بيانات الخصوصية.",
|
||||
"id.verification.privacy.need.photo.question": "Why does {siteName} need my photo?",
|
||||
"id.verification.privacy.need.photo.answer": "نستخدم صور التحقق الخاصة بك لتأكيد هويتك والتأكد من صحة شهادتك.",
|
||||
"id.verification.privacy.do.with.photo.question": "What does {siteName} do with this photo?",
|
||||
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on {siteName} after the verification process is complete.",
|
||||
"id.verification.requirements.card.device.allow": "السماح",
|
||||
"id.verification.requirements.card.id.title": "بطاقة تعريف بها صورة",
|
||||
"id.verification.requirements.card.id.text": "تحتاج إلى بطاقة تعريف صالحة تحتوي اسمك الكامل وصورتك، كرخصة القيادة أو جواز السفر مثلاً.",
|
||||
"id.verification.privacy.title": "معلومات الخصوصية",
|
||||
"id.verification.privacy.need.photo.question": "ما حاجة {siteName} لصورتي؟",
|
||||
"id.verification.privacy.need.photo.answer": "نستخدم صور التحقق الخاصة بك لتأكيد هويتك ولضمان صحة شهادتك.",
|
||||
"id.verification.privacy.do.with.photo.question": "ماذا يُفعَل بهذه الصورة في {siteName}؟",
|
||||
"id.verification.privacy.do.with.photo.answer": "نقوم بتشفير صورتك بشكل آمن وإرسالها إلى خدمة الترخيص الخاصة بنا للمراجعة. صورتك و معلوماتك لا تُحفَظ و لا تظهر في أي مكان على {siteName} بعد اكتمال عملية التحقق.",
|
||||
"id.verification.access.blocked.title": "التحقق من الهوية",
|
||||
"id.verification.access.blocked.enrollment": "أنت الآن ملتحق بمساق يتطلب التحقق من الهوية.",
|
||||
"id.verification.access.blocked.pending": "لقد قمت بالفعل بإرسال معلومات التحقق الخاصة بك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام).",
|
||||
"id.verification.access.blocked.enrollment": "أنت حاليا غير ملتحق بأي مساق يتطلب التحقق من الهوية.",
|
||||
"id.verification.access.blocked.pending": "لقد سلّمت من قبل معلومات التحقق الخاصة بك. ستصلك رسالة على لوحة المعلومات الخاصة بك عند اكتمال عملية التحقق (عادةً في ظرف 5 أيام).",
|
||||
"id.verification.photo.take": "التقاط صورة ",
|
||||
"id.verification.photo.retake": "Retake Photo?",
|
||||
"id.verification.photo.enable.detection": "تمكين خاصية التعرف على الوجه",
|
||||
"id.verification.photo.enable.detection.portrait.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول وجهك. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
|
||||
"id.verification.photo.enable.detection.id.help.text": "عند تحديد هذا الخيار، فسيظهر مربع حول صورة وجهك في بطاقة الهوية. يمكن رؤية وجهك بوضوح إذا كان المربع المحيط به أزرق. إذا لم يكن وجهك في وضع جيد أو إذا لم يكن قابلاً للاكتشاف، فسيكون المربع باللون الأحمر.",
|
||||
"id.verification.photo.feedback.correct": "وضع الوجه جيد.",
|
||||
"id.verification.photo.feedback.two.faces": "تم تحديد أكثر من وجه.",
|
||||
"id.verification.photo.feedback.no.faces": "لم يتم تحديد أي وجه.",
|
||||
"id.verification.photo.feedback.top.left": "وضع خاطئ. أعلى اليسار.",
|
||||
"id.verification.photo.feedback.top.center": "وضع خاطئ. أعلى الوسط.",
|
||||
"id.verification.photo.feedback.top.right": "وضع خاطئ. أعلى اليمين.",
|
||||
"id.verification.photo.feedback.center.left": "وضع خاطئ. وسط اليسار.",
|
||||
"id.verification.photo.feedback.center.center": "وضع خاطئ. قريب جدًا من الكاميرا.",
|
||||
"id.verification.photo.feedback.center.right": "وضع خاطئ. وسط اليمين.",
|
||||
"id.verification.photo.feedback.bottom.left": "وضع خاطئ. أسفل اليسار.",
|
||||
"id.verification.photo.feedback.bottom.center": "وضع خاطئ. أسفل الوسط.",
|
||||
"id.verification.photo.feedback.bottom.right": "وضع خاطئ. أسفل اليمين.",
|
||||
"id.verification.photo.retake": "إعادة التقاط الصورة؟",
|
||||
"id.verification.photo.enable.detection": "تفعيل اكتشاف الوجوه",
|
||||
"id.verification.photo.enable.detection.portrait.help.text": "في حال التأشير، سيبرز مربع حول وجهك. يمكن رؤية وجهك بوضوح إن كان المربع المحيط به أزرق اللون. أما إن كان وجهك في وضع غير جيد أو غير قابل للاكتشاف، فسيكون المربع أحمر اللون.",
|
||||
"id.verification.photo.enable.detection.id.help.text": "في حال التأشير، سيظهر مربع حول صورة وجهك في بطاقة الهوية. يمكن رؤية وجهك بوضوح إن كان المربع المحيط به أزرق اللون. أما إن كان وجهك في وضع غير جيد أو غير قابل للاكتشاف، فسيكون المربع أحمر اللون.",
|
||||
"id.verification.photo.feedback.correct": "موضع الوجه جيد.",
|
||||
"id.verification.photo.feedback.two.faces": "تم اكتشاف أكثر من وجه واحد.",
|
||||
"id.verification.photo.feedback.no.faces": "لم يتم اكتشاف أي وجه.",
|
||||
"id.verification.photo.feedback.top.left": "الموضع خاطئ. أعلى اليسار.",
|
||||
"id.verification.photo.feedback.top.center": "الموضع خاطئ. أعلى الوسط.",
|
||||
"id.verification.photo.feedback.top.right": "الموضع خاطئ. أعلى اليمين.",
|
||||
"id.verification.photo.feedback.center.left": "الموضع خاطئ. وسط اليسار.",
|
||||
"id.verification.photo.feedback.center.center": "الموضع خاطئ. قريب جدًا من الكاميرا.",
|
||||
"id.verification.photo.feedback.center.right": "الموضع خاطئ. وسط اليمين.",
|
||||
"id.verification.photo.feedback.bottom.left": "الموضع خاطئ. أسفل اليسار.",
|
||||
"id.verification.photo.feedback.bottom.center": "الموضع خاطئ. أسفل الوسط.",
|
||||
"id.verification.photo.feedback.bottom.right": "الموضع خاطئ. أسفل اليمين.",
|
||||
"id.verification.camera.access.title": "صلاحيات الكاميرا",
|
||||
"id.verification.camera.access.title.success": "تمكين الوصول للكاميرا",
|
||||
"id.verification.camera.access.title.success": "الوصول للكاميرا ممكن",
|
||||
"id.verification.camera.access.title.failed": "تعذّر الوصول للكاميرا.",
|
||||
"id.verification.camera.access.click.allow": "فضلًا تأكد من اختيار الأمر \"السماح\"",
|
||||
"id.verification.camera.access.enable": "تمكين الكاميرا",
|
||||
"id.verification.camera.access.problems": "هل تواجه أية مشكلة؟",
|
||||
"id.verification.camera.access.skip": "قم بتخطي ملفات الصور وتحميلها بدلاً من ذلك",
|
||||
"id.verification.camera.access.success": "يبدو أن الكاميرا تعمل وجاهزة.",
|
||||
"id.verification.camera.access.failure": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. ستحتاج إلى تحميل ملفات الصور الخاصة بك و معرّف الصور الخاص بك.",
|
||||
"id.verification.camera.access.failure.temporary": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. يرجى التحقق من أن كاميرا الويب متصلة ومن أنك سمحت للمتصفح بالوصول إليها.",
|
||||
"id.verification.camera.access.failure.temporary.chrome": "تمكين الوصول للكاميرا في متصفّح كروم: ",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step1": "فتح متصفّح كروم.",
|
||||
"id.verification.camera.access.click.allow": "رجاءً تأكد من النقر على \"السماح\"",
|
||||
"id.verification.camera.access.enable": "تفعيل الكاميرا",
|
||||
"id.verification.camera.access.problems": "لديك مشاكل؟",
|
||||
"id.verification.camera.access.skip": "تَخَطَّ و قم برفع ملفات صور بدلاً من ذلك.",
|
||||
"id.verification.camera.access.success": "يبدو أن كاميرا جهازك جاهزة و تعمل.",
|
||||
"id.verification.camera.access.failure": "يبدو أننا غير قادرين على الوصول إلى كاميرا جهازك. ستحتاج لرفع ملف صورتك و صورة بطاقة هويتك.",
|
||||
"id.verification.camera.access.failure.temporary": "يبدو أننا غير قادرين على الوصول إلى الكاميرا. رجاءً تحقق من أن كاميرا جهازك متصلة ومن أنك قد سمحت للمتصفح بالوصول إليها.",
|
||||
"id.verification.camera.access.failure.temporary.chrome": "لتفعيل الوصول للكاميرا في متصفح كروم:",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step1": "افتح كروم.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2": "توجه إلى المزيد > الإعدادات.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "لنظام ويندوز: Alt+F أو Alt+E أو F10 متبوعاً بـ SPACEBAR",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "لنظام ماك: Command+,",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step3": "ضمن علامة التبويب \"الخصوصية والأمان\"، حدد \"إعدادات الموقع\" ثم \"الكاميرا\".",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step3": "تحت علامة التبويب \"الخصوصية والأمان\"، اختر \"إعدادات الموقع\" ثم \"الكاميرا\".",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step4": "ضمن \"قائمة الحظر\"، ابحث عن \"edx.org\" وحدده.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step5": "في القسم \"الصلاحيات\"، قم بتحديث صلاحيات الكاميرا إلى \"السماح\".",
|
||||
"id.verification.camera.access.failure.temporary.ie11": "تمكين الوصول للكاميرا في متصفح انترنت إكسبلورر:",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step5": "في قسم \"الصلاحيات\"، قم بتحديث صلاحيات الكاميرا إلى \"السماح\".",
|
||||
"id.verification.camera.access.failure.temporary.ie11": "لتفعيل الوصول للكاميرا في متصفح انترنت إكسبلورر:",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step1": "افتح إدارة إعدادات Flash Player عن طريق الانتقال إلى إعدادات Windows > لوحة التحكم > Flash Player.",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step2": "حدد علامة التبويب \"Camera and Mic\" (الكاميرا والميكروفون)، ثم حدد الزر \"Camera and Microphone Settings by Site\" (إعدادات الكاميرا والميكروفون حسب الموقع).",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step3": "اختر \"edx.org\" من قائمة مواقع ويب وقم بتغيير الصلاحيات من خلال تحديد \"السماح\" في القائمة المنسدلة.",
|
||||
@@ -270,94 +187,171 @@
|
||||
"id.verification.camera.access.failure.temporary.firefox.step7": "اختر \"حفظ التغييرات.\"",
|
||||
"id.verification.camera.access.failure.temporary.safari": "تمكين الوصول للكاميرا في متصفّح سفاري: ",
|
||||
"id.verification.camera.access.failure.temporary.safari.step1": "فتح متصفّح سفاري.",
|
||||
"id.verification.camera.access.failure.temporary.safari.step2": "انقر فوق قائمة تطبيق سفاري، ثم حدد \"Preferences\" (التفضيلات). يمكنك أيضاً استخدام الأمرCommand+ كاختصار للوحة المفاتيح",
|
||||
"id.verification.camera.access.failure.temporary.safari.step3": "حدد علامة التبويب \"مواقع ويب\" ثم حدد \"كاميرا\".",
|
||||
"id.verification.camera.access.failure.temporary.safari.step2": "انقر فوق قائمة تطبيق سفاري، ثم حدد \"Preferences\" (التفضيلات). يمكنك أيضاً استخدام الأمرCommand+, كاختصار للوحة المفاتيح",
|
||||
"id.verification.camera.access.failure.temporary.safari.step3": "حدد علامة التبويب \"مواقع الويب\" ثم حدد \"كاميرا\".",
|
||||
"id.verification.camera.access.failure.temporary.safari.step4": "حدد \"edx.org\" وقم بتغيير صلاحيات الكاميرا إلى \"السماح\".",
|
||||
"id.verification.camera.access.failure.unsupported": "It looks like your browser does not support camera access.",
|
||||
"id.verification.camera.access.failure.unsupported.chrome.explanation": "The Chrome browser currently does not support camera access on iOS devices, such as iPhones and iPads.",
|
||||
"id.verification.camera.access.failure.unsupported.instructions": "Please use another browser to complete Identity Verification.",
|
||||
"id.verification.photo.tips.title": "تلميحات مفيدة للصورة",
|
||||
"id.verification.photo.tips.description": "بعد ذلك، سنحتاج منك التقاط صورة لوجهك. يرجى مراجعة التلميحات المفيدة أدناه.",
|
||||
"id.verification.photo.tips.list.title": "تلميحات الصورة",
|
||||
"id.verification.camera.access.failure.unsupported": "يبدو أن متصفحك لا يدعم الوصول إلى الكاميرا.",
|
||||
"id.verification.camera.access.failure.unsupported.chrome.explanation": "لا يدعم متصفح Chrome حاليًا الوصول إلى الكاميرا على أجهزة iOS مثل أجهزة iPhone و iPad.",
|
||||
"id.verification.camera.access.failure.unsupported.instructions": "رجاءً استخدم متصفحًا آخر لإكمال التحقق من الهوية.",
|
||||
"id.verification.photo.tips.title": "نصائح مفيدة بخصوص الصورة",
|
||||
"id.verification.photo.tips.description": "بعد ذلك، سنحتاج منك التقاط صورة لوجهك. يرجى مراجعة النصائح المفيدة أدناه.",
|
||||
"id.verification.photo.tips.list.title": "نصائح بخصوص الصورة",
|
||||
"id.verification.photo.tips.list.description": "لالتقاط صورة ناجحة، يُرجى التأكّد ممّا يلي:",
|
||||
"id.verification.photo.tips.list.well.lit": "أنّ الإضاءة جيّدة على وجهك.",
|
||||
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك داخل إطار الصورة بالكامل.",
|
||||
"id.verification.photo.tips.list.well.lit": "أنّ وجهك مُضاء جيدًا.",
|
||||
"id.verification.photo.tips.list.inside.frame": "أنّ وجهك كلَّه داخل إطار الصورة.",
|
||||
"id.verification.portrait.photo.title.camera": "التقط صورة لنفسك",
|
||||
"id.verification.portrait.photo.title.upload": "ارفع صورتك",
|
||||
"id.verification.portrait.photo.preview.alt": "معاينة صورة وجه المستخدم.",
|
||||
"id.verification.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر التقاط صورة أدناه لالتقاط الصورة.",
|
||||
"id.verification.portrait.photo.instructions.upload": "Please upload a portrait photo. Ensure your entire face fits inside the frame and is well-lit. Supported formats: ",
|
||||
"id.verification.camera.help.sight.question": "ماذا لو لم أتمكن من رؤية صورة الكاميرا ؟ أو إذا لم أتمكن من رؤية صورتي لتحديد أي جانب مرئي؟",
|
||||
"id.verification.camera.help.sight.answer.portrait": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات ضبط وضع الكاميرا بشكل صحيح. يختلف وضع الكاميرا المثالي باختلاف الكمبيوتر، ولكن بشكل عام، يكون أفضل موضع للتصوير في الرأس هو 12 إلى 18 بوصة (30-45 سم) تقريبًا من الكاميرا، مع وضع الرأس في المنتصف بالنسبة إلى شاشة الكمبيوتر. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة.",
|
||||
"id.verification.camera.help.sight.answer.id": "قد تتمكن من إكمال إجراء التقاط الصور من دون مساعدة، ولكن قد يتطلب الأمر بضع محاولات لضبط وضع الكاميرا بشكل صحيح. يختلف الوضع الأمثل للكاميرا باختلاف جهاز الكمبيوتر، ولكن بشكل عام، يكون أفضل وضع لصورة بطاقة تعريف من 8 إلى 12 بوصة (من 20 إلى 30 سم) عن الكاميرا، مع وضع بطاقة الهوية في الوسط بالنسبة للكاميرا. إذا تم رفض الصور التي ترسلها، فحاول تحريك اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة. إن السبب الأكثر شيوعاً للرفض هو عدم القدرة على قراءة النص الموجود على بطاقة الهوية.",
|
||||
"id.verification.portrait.photo.instructions.camera": "عندما يكون وجهك في موضعه، استخدم زر 'التقاط صورة' أدناه لالتقاط صورتك.",
|
||||
"id.verification.camera.help.sight.question": "ماذا إن لم أتمكن من رؤية صورة الكاميرا أو إن لم أتمكن من رؤية صورتي لتحديد أي جانب مرئي؟",
|
||||
"id.verification.camera.help.sight.answer.portrait": "قد تتمكن من إكمال إجراء التقاط الصور دون مساعدة، لكن قد يتطلب الأمر بضع محاولات لضبط موضع الكاميرا بشكل صحيح.يختلف موضع الكاميرا المثالي من حاسوب ﻵخر، لكن عمومًا يكون أفضل موضع لتصوير الرأس تقريبًا على بعد 12-18 بوصة (30-45 سنتمترًا) من الكاميرا، مع وضع رأسك في المنتصف بالنسبة إلى شاشة الحاسوب. إن تم رفض الصور التي ترسلها، فحاول تغيير اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة.",
|
||||
"id.verification.camera.help.sight.answer.id": "قد تتمكن من إكمال إجراء التقاط الصور دون مساعدة، لكن قد يتطلب الأمر بضع محاولات لضبط موضع الكاميرا بشكل صحيح.يختلف موضع الكاميرا المثالي من حاسوب ﻵخر، لكن عمومًا يكون أفضل موضع لتصوير بطاقة تعريف تقريبًا على بعد 8-12 بوصة (20-30 سنتمترًا) من الكاميرا، مع وضع البطاقة في المنتصف بالنسبة للكاميرا. إن تم رفض الصور التي ترسلها، فحاول تغيير اتجاه الكمبيوتر أو الكاميرا لتغيير زاوية الإضاءة. إن أكثر سبب للرفض هو عدم القدرة على قراءة نص بطاقة التعريف.",
|
||||
"id.verification.camera.help.difficulty.question.portrait": "ماذا لو واجهت صعوبة في تثبيت رأسي في الموضع المناسب للكاميرا؟",
|
||||
"id.verification.camera.help.difficulty.question.id": "ماذا لو واجهت صعوبة في تثبيت بطاقة هويتي في الموضع المناسب للكاميرا؟",
|
||||
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact {siteName} support for additional suggestions.",
|
||||
"id.verification.camera.help.upload.question": "What if I want to upload a photo instead?",
|
||||
"id.verification.camera.help.upload.answer": "On the next page you will have the option to switch to upload mode. By selecting that option, you will be able to upload a photo instead.",
|
||||
"id.verification.id.photo.unclear.question": "Is your ID card image not clear or too blurry?",
|
||||
"id.verification.id.tips.title": "Helpful Identification Card Tips",
|
||||
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo, such as a driver’s license or passport. Please have your ID ready.",
|
||||
"id.verification.id.tips.list.well.lit": "Your identification card is well-lit.",
|
||||
"id.verification.camera.help.difficulty.answer": "إن احتجت لمساعدة في التقاط صورة لإرسالها، فاتصل بدعم {siteName} للحصول على اقتراحات إضافية.",
|
||||
"id.verification.id.photo.unclear.question": "هل صورة بطاقة تعريفك غير واضحة أو ضبابية جدًا؟",
|
||||
"id.verification.id.tips.title": "نصائح مفيدة بخصوص بطاقة التعريف",
|
||||
"id.verification.id.tips.description": "بعد ذلك، عليك التقاط صورة لبطاقة تعريف صالحة تتضمن اسمك الكامل مع صورة، مثل رخصة القيادة أو جواز السفر.يرجى منك تجهيز بطاقة تعريفك.",
|
||||
"id.verification.id.tips.list.well.lit": "أن تكون بطاقة تعريفك مضاءة جيدًا.",
|
||||
"id.verification.id.tips.list.clear": "تأكد من قدرتك على رؤية صورتك وقراءة اسمك بوضوح.",
|
||||
"id.verification.id.photo.title.camera": "Take a Photo of Your Identification Card",
|
||||
"id.verification.id.photo.title.upload": "Upload a Photo of Your Identification Card",
|
||||
"id.verification.id.photo.title.camera": "التقط صورة لبطاقة تعريفك",
|
||||
"id.verification.id.photo.title.upload": "ارفع صورة لبطاقة تعريفك",
|
||||
"id.verification.id.photo.preview.alt": "معاينة صورة الهوية.",
|
||||
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo. Please use a passport, driver’s license, or another identification card that includes your full name and a picture of your face.",
|
||||
"id.verification.id.photo.instructions.upload": "Please upload a photo of your identification card. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. Supported formats: ",
|
||||
"id.verification.id.photo.instructions.upload.error.invalidFileType": "The file you have selected is not a supported image type. Please choose from the following formats: ",
|
||||
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "The file you have selected is too large. Please try again with a file less than 10MB.",
|
||||
"id.verification.account.name.title": "التحقق من اسم الحساب",
|
||||
"id.verification.name.check.title": "Double-Check Your Name",
|
||||
"id.verification.account.name.instructions": "يجب أن يكون الاسم الموجود على حسابك والاسم الموجود على المعرّف الخاص بك متطابقًا تمامًا. إذا لم يكن الأمر كذلك، فيرجى النقر فوق \"لا\" لتحديث اسم حسابك.",
|
||||
"id.verification.name.check.instructions": "Does the name below match the name on your government-issued ID? If not, update the name below to match your goverment-issued ID.",
|
||||
"id.verification.name.check.mismatch.information": "If the name below does not match your government-issued ID, your identity verification will be denied.",
|
||||
"id.verification.account.name.radio.label": "هل يتطابق الاسم الموجود على هويتك مع اسم الحساب أدناه؟",
|
||||
"id.verification.name.check.radio.label": "Select an option",
|
||||
"id.verification.account.name.radio.yes": "نعم",
|
||||
"id.verification.name.check.radio.yes": "Yes, the name below matches my ID",
|
||||
"id.verification.account.name.radio.no": "لا",
|
||||
"id.verification.name.check.radio.no": "No, the name below does not match my ID",
|
||||
"id.verification.account.name.error": "يرجى تحديث اسم الحساب لمطابقة الاسم على بطاقة الهوية.",
|
||||
"id.verification.account.name.warning.prefix": "يُرجى الملاحظة:",
|
||||
"id.verification.id.photo.instructions.camera": "عندما تكون بطاقتك في موضعها، استخدم زر 'التقاط صورة' أدناه لالتقاط صورتك. يرجى استخدام جواز سفر أو رخصة قيادة أو بطاقة تعريف أخرى تتضمن اسمك الكامل وصورة لوجهك.",
|
||||
"id.verification.id.photo.instructions.upload": "Please upload a photo of your identification card. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. Supported formats:",
|
||||
"id.verification.id.photo.instructions.upload.error.invalidFileType": "The file you have selected is not a supported image type. Please choose from the following formats:",
|
||||
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "الملف الذي حددته كبير جداً. رجاءً أعد المحاولة باستخدام ملف ذي حجم أقل من 10 ميجابايت.",
|
||||
"id.verification.name.check.title": "تحقق مرة أخرى من اسمك",
|
||||
"id.verification.name.check.instructions": "هل الاسم أدناه يطابق الاسم الموجود في بطاقة تعريفك ذات الصورة. إن لم يكن كذلك، فقم بتحديث الاسم أدناه ليطابق بطاقة تعريفك.",
|
||||
"id.verification.name.check.mismatch.information": "إن كان الاسم أدناه لا يطابق بطاقة تعريفك، فسيتم رفض تأكيد هويتك.",
|
||||
"id.verification.name.error": "رجاءً أدخل اسمك كما يظهر في بطاقة تعريفك ذات الصورة.",
|
||||
"id.verification.account.name.warning.prefix": "تُرجى الملاحظة:",
|
||||
"id.verification.account.name.settings": "إعدادات الحساب",
|
||||
"id.verification.account.name.label": "اسم الحساب",
|
||||
"id.verification.name.label": "Name",
|
||||
"id.verification.account.name.photo.alt": "صورة من هويتك للتقديم.",
|
||||
"id.verification.account.name.save": "حفظ ثم التالي",
|
||||
"id.verification.name.label": "الاسم",
|
||||
"id.verification.account.name.photo.alt": "صورة بطاقة هويتك التي ستُسلَّم.",
|
||||
"id.verification.review.title": "مراجعة صورك",
|
||||
"id.verification.review.description": "يُرجى التأكّد من أنّ الصور والمعلومات التي قدّمتها تمكّننا من التحقّق من هويّتك. ",
|
||||
"id.verification.review.portrait.label": "صورتك الشخصية",
|
||||
"id.verification.review.portrait.alt": "صورة شخصية للتقديم.",
|
||||
"id.verification.review.portrait.retake": "إعادة التقاط الصورة شخصية",
|
||||
"id.verification.review.id.label": "Your Identification Card",
|
||||
"id.verification.review.id.alt": "Photo of your identification card to be submitted.",
|
||||
"id.verification.review.id.retake": "إعادة التقاط صورة الهوية",
|
||||
"id.verification.review.confirm": "إرسال",
|
||||
"id.verification.submission.alert.error.face": "مطلوب صورة لوجهك. يرجى إعادة التقاط الصورة الشخصية.",
|
||||
"id.verification.submission.alert.error.id": "مطلوب صورة لبطاقة هويتك. يرجى إعادة التقاط صورة بطاقة الهوية.",
|
||||
"id.verification.submission.alert.error.name": "مطلوب اسم حساب صالح. يرجى تحديث اسم حسابك لمطابقة الاسم على هويتك.",
|
||||
"id.verification.submission.alert.error.unsupported": "One or more of the files you have uploaded is in an unsupported format. Please choose from the following: ",
|
||||
"id.verification.review.error": "{siteName} Support Page",
|
||||
"id.verification.submitted.title": "جارِ التحقق من الهوية",
|
||||
"id.verification.submitted.text": "لقد تلقينا معلوماتك وجاري الآن العمل على التحقق من هويتك. ستصلك رسالة على لوحة المعلومات عند اكتمال عملية التحقق (عادةً خلال 5 أيام). في غضون ذلك، لا يزال بإمكانك الوصول إلى كل محتوى المساق المتوفر.",
|
||||
"id.verification.return.dashboard": "العودة إلى لوحة المعلومات",
|
||||
"id.verification.review.portrait.alt": "صورة وجهك التي ستُسلَّم.",
|
||||
"id.verification.review.portrait.retake": "إعادة التقاط الصورة الشخصية",
|
||||
"id.verification.review.id.label": "بطاقة تعريفك",
|
||||
"id.verification.review.id.alt": "صورة بطاقة تعريفك التي ستُسلَّم.",
|
||||
"id.verification.review.id.retake": "إعادة التقاط صورة بطاقة الهوية",
|
||||
"id.verification.review.confirm": "تسليم",
|
||||
"id.verification.submission.alert.error.face": "مطلوبة صورة لوجهك. رجاءً أعد التقاط صورتك الشخصية.",
|
||||
"id.verification.submission.alert.error.id": "مطلوبة صورة لبطاقة تعريفك. رجاءً أعد التقاط صورة لبطاقة تعريفك.",
|
||||
"id.verification.submission.alert.error.name": "مطلوب اسم حساب صحيح. يرجى تحديث اسم حسابك لمطابقة الاسم على بطاقة تعريفك.",
|
||||
"id.verification.submission.alert.error.unsupported": "One or more of the files you have uploaded is in an unsupported format. Please choose from the following:",
|
||||
"id.verification.review.error": "{siteName} صفحة دعم",
|
||||
"id.verification.submitted.title": "التحقق من الهوية جارٍ",
|
||||
"id.verification.submitted.text": "لقد تلقينا معلوماتك و نحن الآن نتحقق من هويتك. سيتم إخطارك عند اكتمال عملية التحقق (عادةً في ظرف 5 أيام). إلى ذلكم الحين، لا يزال يمكنك الوصول لجميع محتويات المساق المتاحة.",
|
||||
"id.verification.return.dashboard": "العودة للوحة المعلومات",
|
||||
"id.verification.return.course": "العودة للمساق",
|
||||
"id.verification.photo.upload.help.title": "Upload a Photo Instead",
|
||||
"id.verification.photo.camera.help.title": "Use Your Camera Instead",
|
||||
"id.verification.photo.upload.help.text": "If you are having trouble using the photo capture above, you may want to upload a photo instead. To upload a photo, click the button below.",
|
||||
"id.verification.photo.camera.help.text": "If you are having trouble uploading a photo above, you may want to use your camera instead. To use your camera, click the button below.",
|
||||
"id.verification.upload.help.button": "Switch to Upload Mode",
|
||||
"id.verification.camera.help.button": "Switch to Camera Mode",
|
||||
"id.verification.choose.mode.title": "Photo Requirements Options",
|
||||
"id.verification.choose.mode.hep.text": "To complete verification, please select one of the following options to submit photos. You will be able to switch between these options throughout the process if needed.",
|
||||
"id.verification.choose.mode.radio.upload": "Upload photos from my device",
|
||||
"id.verification.choose.mode.radio.camera": "Take pictures using my camera",
|
||||
"id.verification.account.name.managed.alert": "Your profile settings are managed by {managerTitle}, so you are not allowed to update your name. Please contact your {profileDataManager} administrator or {support} for help.",
|
||||
"id.verification.request.camera.access.instructions": "لالتقاط صورة باستخدام كاميرا الويب، قد تتلقى طلب المتصفح للوصول إلى الكاميرا. {clickAllow}",
|
||||
"id.verification.requirements.account.managed.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help before completing the Photo Verification process.",
|
||||
"id.verification.requirements.card.device.text": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلب المتصفح للوصول إلى الكاميرا، فيرجى التأكد من النقر فوق {السماح}.",
|
||||
"id.verification.account.name.summary.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help.",
|
||||
"idv.submission.alert.error": "\nواجهنا خطأ فني أثناء محاولة رفع طلب التحقق من الهوية\nقد تكون هذه مشكلة مؤقتة، لذا يرجى المحاولة مرة أخرى بعد بضع دقائق\nعند استمرار المشكلة يرجى الانتقال إلى {support_link} للحصول على المساعدة",
|
||||
"id.verification.account.name.edit": "Edit {sr}"
|
||||
"id.verification.return.generic": "العودة",
|
||||
"id.verification.photo.upload.help.title": "قم يدلا من هذا برفع صورة",
|
||||
"id.verification.photo.camera.help.title": "استخدم الكاميرا بدلاً من هذا",
|
||||
"id.verification.photo.upload.help.text": "إن واجهتك مشكلة في استخدام مُلتقط الصور أعلاه، فقد ترغب بدﻷ من ذلك في رفع صورة. لرفع صورة، انقر على الزر أدناه.",
|
||||
"id.verification.photo.camera.help.text": "إن واجهتك مشكلة في رفع صورة أعلاه، فقد ترغب بدلا من ذلك في استخدام كاميرا جهازك. لاستخدام الكاميرا، انقر على الزر أدناه.",
|
||||
"id.verification.upload.help.button": "انتقل إلى وضع الرفع",
|
||||
"id.verification.camera.help.button": "انتقل إلى وضع الكاميرا",
|
||||
"notification.preference.heading": "Notifications",
|
||||
"notification.preference.app.title": "{ key, select, discussion {Discussions} coursework {Course Work} other {{key}} }",
|
||||
"notification.preference.title": "{ text, select, core {Core notifications} newDiscussionPost {New discussion posts} newQuestionPost {New question posts} other {{text}} }",
|
||||
"notification.preference.type.label": "Type",
|
||||
"notification.preference.web.label": "Web",
|
||||
"notification.preference.help.email": "Email",
|
||||
"notification.preference.help.push": "Push",
|
||||
"notification.preference.load.more.courses": "Load more courses",
|
||||
"notification.preference.guide.link": "as detailed here",
|
||||
"notification.preference.guide.body": "Notifications for certain activities are enabled by default,",
|
||||
"account.settings.field.name.certificate.select": "في حال التأشير، سيظهر هذا الاسم في شهاداتك و سجلاتك العامة.",
|
||||
"account.settings.field.name.modal.certificate.title": "اختر اسمًا مفضلًا للشهادات والسجلات العامة",
|
||||
"account.settings.field.name.modal.certificate.select": "اختر اسمًا",
|
||||
"account.settings.field.name.modal.certificate.option.full": "الاسم الكامل",
|
||||
"account.settings.field.name.modal.certificate.option.verified": "اسم متحقَّق منه",
|
||||
"account.settings.field.name.modal.certificate.button.choose": "اختيار الاسم",
|
||||
"account.settings.delete.account.before.proceeding": "قبل المتابعة، يرجى {actionLink}.",
|
||||
"account.settings.delete.account.text.3.edX": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى مثل شهادات MicroMasters. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف. {actionLink}.",
|
||||
"account.settings.delete.account.text.3": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف.",
|
||||
"account.settings.delete.account.header": "حذف حسابي",
|
||||
"account.settings.delete.account.subheader": "نأسف لذهابك!",
|
||||
"account.settings.delete.account.text.1": "ترجى الملاحظة: إن حذف حسابك وبياناتك الشخصية له أثر دائم و لا يمكن التراجع عنه. لن يكون بمقدور {siteName} استعادة حسابك ولا البيانات التي يتم حذفها.",
|
||||
"account.settings.delete.account.text.2": "بمجرد حذف حسابك، فإنك لن تستطيع استخدامه لمتابعة المساقات على {siteName}.",
|
||||
"account.settings.delete.account.text.2.edX": "بمجرد حذف حسابك، فإنك لن تستطيع استخدامه لمتابعة المساقات على تطبيق edX ولا edx.org ولا أي موقع آخر تستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة و الوصول إلى المواقع الخاصة التي تقدمها MIT Open Learning و Wharton Executive Education و Harvard Medical School.",
|
||||
"account.settings.delete.account.text.3.link": "اتّبع هذه التعليمات لطباعة أو تحميل شهادة",
|
||||
"account.settings.delete.account.text.warning": "تحذير: حذف الحساب أثره دائم. يرجى قراءة ما ورد أعلاه بعناية قبل المتابعة. هذا إجراء غير رجعي، و لن تتمكن بعده من استخدام نفس البريد الإلكتروني على {siteName}.",
|
||||
"account.settings.delete.account.text.change.instead": "هل تريد بدلاً من ذلك تغيير بريدك الإلكتروني أو اسمك أو كلمة المرور الخاصة بك؟",
|
||||
"account.settings.delete.account.button": "حذف حسابي",
|
||||
"account.settings.delete.account.please.activate": "تفعيل حسابك",
|
||||
"account.settings.delete.account.please.confirm": "تأكيد حسابك",
|
||||
"account.settings.delete.account.please.unlink": "فصل جميع حسابات التواصل الاجتماعي",
|
||||
"account.settings.delete.account.modal.header": "هل أنت متأكد؟",
|
||||
"account.settings.delete.account.modal.text.1": "لقد اخترت \"حذف حسابي\". إن حذف حسابك وبياناتك الشخصية ذو أثر دائم لا يمكن التراجع عنه. لن يكون بمقدور {siteName} استعادة حسابك و لا البيانات التي حذفت.",
|
||||
"account.settings.delete.account.modal.text.2": "إن واصلت، فلن تستطيع استخدام هذا الحساب لمتابعة المساقات على {siteName}.",
|
||||
"account.settings.delete.account.modal.text.2.edX": "إن واصلت، فلن تستطيع استخدام هذا الحساب لمتابعة المساقات على على تطبيق edX و لا edx.org و لا أي موقع آخر تستضيفه edX. وهذا يشمل الوصول إلى edx.org من نظام صاحب العمل أو الجامعة و الوصول إلى المواقع الخاصة التي يقدمها MIT Open Learning و Wharton Executive Education و Harvard Medical School.",
|
||||
"account.settings.delete.account.modal.enter.password": "إن كنت لا تزال ترغب في المتابعة و حذف حسابك، فيرجى إدخال كلمة المرور:",
|
||||
"account.settings.delete.account.modal.confirm.delete": "تعم، احذف",
|
||||
"account.settings.delete.account.modal.confirm.cancel": "لا",
|
||||
"account.settings.delete.account.error.unable.to.delete": "لم نستطع حذف الحساب",
|
||||
"account.settings.delete.account.error.no.password": "كلمة المرور مطلوبة",
|
||||
"account.settings.delete.account.error.invalid.password": "كلمة المرور غير صحيحة",
|
||||
"account.settings.delete.account.error.unable.to.delete.details": "عذراً، حدث خطأ أثناء محاولة معالجة طلبك. رجاءً أعد المحاولة لاحقًا.",
|
||||
"account.settings.delete.account.modal.after.header": "We're sorry to see you go! Your account will be deleted shortly.",
|
||||
"account.settings.delete.account.modal.after.text": "حذف الحساب، بما في ذلك من إزالة من القوائم البريدية، إجراء قد يستغرق بضعة أسابيع حتى يكتمل عبر نظامنا. إن كنت تريد قبل ذلك الحين إيقاف تلقي البريد الإلكتروني، فيرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
|
||||
"account.settings.delete.account.modal.after.button": "إغلاق ",
|
||||
"account.settings.message.demographics.service.issue": "حدث خطأ أثناء محاولة استخراج أو حفظ معلومات حسابك. رجاءً أعد المحاولة لاحقًا.",
|
||||
"account.settings.field.demographics.gender": "هوية الجنس",
|
||||
"account.settings.field.demographics.gender.empty": "إضافة هوية الجنس",
|
||||
"account.settings.field.demographics.gender.options.empty": "حدد هوية الجنس",
|
||||
"account.settings.field.demographics.gender_description": "وصف هوية الجنس",
|
||||
"account.settings.field.demographics.gender_description.empty": "أدخل الوصف",
|
||||
"account.settings.field.demographics.ethnicity": "هوية العرق/ الأصل",
|
||||
"account.settings.field.demographics.ethnicity.empty": "إضافة هوية العرق / الأصل",
|
||||
"account.settings.field.demographics.ethnicity.options.empty": "اختر كل ما ينطبق",
|
||||
"account.settings.field.demographics.income": "دخل الأسرة",
|
||||
"account.settings.field.demographics.income.empty": "إضافة دخل الأسرة",
|
||||
"account.settings.field.demographics.income.options.empty": "حدد نطاقًا لدخل الأسرة",
|
||||
"account.settings.field.demographics.military_history": "الوضعية إزاء الخدمة العسكرية في الولايات المتحدة",
|
||||
"account.settings.field.demographics.military_history.empty": "إضافة الوضعية إزاء الخدمة العسكرية",
|
||||
"account.settings.field.demographics.military_history.options.empty": "اختر الوضعية إزاء الخدمة العسكرية",
|
||||
"account.settings.field.demographics.learner_education_level": "مستواك التعليمي",
|
||||
"account.settings.field.demographics.learner_education_level.empty": "إضافة المستوى التعليمي",
|
||||
"account.settings.field.demographics.parent_education_level": "مستوى الوالدين/الأولياء التعليمي",
|
||||
"account.settings.field.demographics.parent_education_level.empty": "إضافة المستوى التعليمي",
|
||||
"account.settings.field.demographics.education_level.options.empty": "حدد المستوى التعليمي",
|
||||
"account.settings.field.demographics.work_status": "الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status.empty": "إضافة الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status.options.empty": "حدد الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status_description": "وصف الحالة الوظيفية",
|
||||
"account.settings.field.demographics.work_status_description.empty": "أدخل الوصف",
|
||||
"account.settings.field.demographics.current_work_sector": "مجال العمل الحالي",
|
||||
"account.settings.field.demographics.current_work_sector.empty": "إضافة مجال العمل",
|
||||
"account.settings.field.demographics.future_work_sector": "مجال العمل المستقبلي",
|
||||
"account.settings.field.demographics.future_work_sector.empty": "إضافة مجال العمل",
|
||||
"account.settings.field.demographics.work_sector.options.empty": "حدد مجال العمل",
|
||||
"account.settings.section.demographics.why": "ما هي غاية {siteName} من جمع هذه المعلومات؟",
|
||||
"account.settings.name.change.title.id": "تغيير الاسم هذا يتطلب التحقق من الهوية",
|
||||
"account.settings.name.change.title.begin": "قبل أن نبدأ",
|
||||
"account.settings.name.change.warning.one": "تحذير: يقوم هذا الإجراء بتحديث الاسم الذي يظهر على جميع الشهادات التي تم الحصول عليها على هذا الحساب في الماضي و أي شهادات تحصل عليها حاليا أو مستقبلاً.",
|
||||
"account.settings.name.change.warning.two": "لا يمكن التراجع عن هذا الإجراء دون التحقق من هويتك.",
|
||||
"account.settings.name.change.id.name.label": "أدخل اسمك كما يظهر في بطاقة تعريف الطالب أو العمل أو بطاقة الهوية الصادرة عن الحكومة.",
|
||||
"account.settings.name.change.id.name.placeholder": "أدخل الاسم الموجود في بطاقة تعريفك ذات الصورة.",
|
||||
"account.settings.name.change.error.valid.name": "رجاءً أدخل اسما صحيحا.",
|
||||
"account.settings.name.change.error.general": "حدث خطأ تقني. رجاءً حاول مجددًا.",
|
||||
"account.settings.name.change.continue": "مواصلة",
|
||||
"account.settings.name.change.cancel": "إلغاء",
|
||||
"account.settings.editable.field.password.reset.button.confirmation.support.link": "الدعم الفني",
|
||||
"account.settings.editable.field.password.reset.button.confirmation": "لقد أرسلنا رسالة إلى {email}. انقر على الرابط في الرسالة لإعادة ضبط كلمة المرور الخاصة بك. لم تصلك الرسالة؟ اتصل بـ{technicalSupportLink}.",
|
||||
"account.settings.editable.field.password.reset.button.forbidden": "طلبك السابق في تقدّم، رجاءً حاول مجددًا بعد لحظات قليلة.",
|
||||
"account.settings.editable.field.password.reset.label": "كلمة المرور",
|
||||
"account.settings.editable.field.password.reset.button": "إعادة ضبط كلمة المرور",
|
||||
"account.settings.sso.link.account": "تسجيل الدخول باستخدام {name}",
|
||||
"account.settings.sso.account.connected": "موصول",
|
||||
"account.settings.sso.account.disconnect.error": "حدثت مشكلة أثناء فصل هذا الحساب، اتصل بالدعم إن استمرت المشكلة.",
|
||||
"account.settings.sso.unlink.account": "فصل حساب {name}",
|
||||
"account.settings.sso.no.providers": "لا يمكن وصل أي حسابات في الوقت الراهن.",
|
||||
"id.verification.request.camera.access.instructions": "حتى تلتقط صورة باستخدام الكاميرا، قد تتلقى طلبًا من المتصفح للوصول إلى الكاميرا. {clickAllow}",
|
||||
"id.verification.requirements.account.managed.alert": "إعدادات حسابك يديرها {managerTitle}. إن لم يكن الاسم في بطاقة هويتك ذات الصورة. مطابقًا للاسم الذي في حسابك، فيرجى الاتصال بالمسؤول {profileDataManager} أو ب{support} للحصول على المساعدة قبل إتمام عملية التحقق من الصورة..",
|
||||
"id.verification.requirements.card.device.text": "أنت بحاجة إلى جهاز مزود بكاميرا. إذا تلقيت طلبًا من المتصفح للوصول إلى كاميرا جهازك، فتأكد رجاءً من النقر على {السماح}.",
|
||||
"id.verification.account.name.summary.alert": "إعدادات حسابك يديرها {managerTitle}. إن لم يكن الاسم في بطاقة هويتك ذات الصورة. مطابقًا للاسم الذي في حسابك، فيرجى الاتصال بالمسؤول {profileDataManager} أو ب{support} للحصول على المساعدة قبل إتمام عملية التحقق من الصورة..",
|
||||
"idv.submission.alert.error": "We encountered a technical error while trying to submit ID verification. This might be a temporary issue, so please try again in a few minutes. If the problem persists, please go to {support_link} for help.",
|
||||
"id.verification.account.name.edit": "تعديل {sr}"
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"siteheader.links.courses": "Courses",
|
||||
"siteheader.links.programs": "Programs & Degrees",
|
||||
"siteheader.links.schools": "Schools & Partners",
|
||||
"siteheader.user.menu.dashboard": "Dashboard",
|
||||
"siteheader.user.menu.profile": "Profile",
|
||||
"siteheader.user.menu.account.settings": "Account",
|
||||
"siteheader.user.menu.logout": "Logout",
|
||||
"siteheader.user.menu.login": "Login",
|
||||
"siteheader.user.menu.register": "Sign Up"
|
||||
}
|
||||
357
src/i18n/messages/de.json
Normal file
357
src/i18n/messages/de.json
Normal file
@@ -0,0 +1,357 @@
|
||||
{
|
||||
"account.settings.message.duplicate.tpa.provider": "The {provider} account you selected is already linked to another {siteName} account.",
|
||||
"account.settings.message.managed.settings": "Your profile settings are managed by {managerTitle}. Contact your administrator or {support} for help.",
|
||||
"account.settings.message.managed.settings.support": "support",
|
||||
"account.settings.page.heading": "Account Settings",
|
||||
"account.settings.loading.message": "Loading...",
|
||||
"account.settings.loading.error": "Error: {error}",
|
||||
"account.settings.banner.beta.language": "You have set your language to {beta_language}, which is currently not fully translated. You can help us translate this language fully by joining the Transifex community and adding translations from English for learners that speak {beta_language}.",
|
||||
"account.settings.banner.beta.language.action.switch.back": "Switch Back to {previous_language}",
|
||||
"account.settings.banner.beta.language.action.help.translate": "Help Translate into {beta_language}",
|
||||
"account.settings.section.account.information": "Account Information",
|
||||
"account.settings.section.account.information.description": "These settings include basic information about your account.",
|
||||
"account.settings.section.profile.information": "Profile Information",
|
||||
"account.settings.section.demographics.information": "Optional Information",
|
||||
"account.settings.section.site.preferences": "Site Preferences",
|
||||
"account.settings.section.linked.accounts": "Linked Accounts",
|
||||
"account.settings.section.linked.accounts.description": "You can link your identity accounts to simplify signing in to {siteName}.",
|
||||
"account.settings.field.username": "Username",
|
||||
"account.settings.field.username.help.text": "The name that identifies you on {siteName}. You cannot change your username.",
|
||||
"account.settings.field.full.name": "Full name",
|
||||
"account.settings.field.full.name.empty": "Add name",
|
||||
"account.settings.field.full.name.help.text": "The name that is used for ID verification and that appears on your certificates.",
|
||||
"account.settings.field.full.name.help.text.default": "The name that appears on your public profile.",
|
||||
"account.settings.field.full.name.help.text.default.certificate": "This name is selected to appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.verified": "Verified name",
|
||||
"account.settings.field.name.verified.help.text.verified": "This name has been verified by photo ID.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored": "This name has been verified by proctoring.",
|
||||
"account.settings.field.name.verified.help.text.verified.certificate": "This name has been verified by photo ID, and is selected to appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "This name has been verified by proctoring, and is selected to appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.verified.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored": "Your proctored exam has been submitted. Verified name cannot be changed at this time. Please check back in 2-5 days.",
|
||||
"account.settings.field.name.verified.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Verified name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "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.",
|
||||
"account.settings.field.name.verified.verification.help": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
|
||||
"account.settings.field.full.name.help.text.submitted": "Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored": "Your proctored exam has been submitted. Full name cannot be changed at this time. Please check back in 2-5 days.",
|
||||
"account.settings.field.full.name.help.text.submitted.certificate": "When identity verification is successful, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Once your proctored exam passes review, this name will appear on your certificates and public-facing records. Full name cannot be changed at this time.",
|
||||
"account.settings.field.name.verified.success.message": "Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.",
|
||||
"account.settings.field.name.verified.success.message.header": "Your name change request is complete!",
|
||||
"account.settings.field.name.verified.failure.message": "Your most recent identity verification attempt did not pass. Related account settings have been restored.",
|
||||
"account.settings.field.name.verified.failure.message.header": "We were not able to verify your identity.",
|
||||
"account.settings.field.name.verified.failure.message.help.link": "Learn more about ID verification",
|
||||
"account.settings.field.name.verified.submitted.message": "Your identity verification request has been submitted and usually takes between 24 and 48 hours to complete.",
|
||||
"account.settings.field.name.verified.submitted.message.certificate": "When your request is approved, your updated name will appear on all associated certificates and public-facing records.",
|
||||
"account.settings.field.name.verified.submitted.message.header": "Your name change request is almost complete!",
|
||||
"account.settings.field.email": "Email address (Sign in)",
|
||||
"account.settings.field.email.empty": "Add email address",
|
||||
"account.settings.field.email.confirmation": "We’ve sent a confirmation message to {value}. Click the link in the message to update your email address.",
|
||||
"account.settings.field.email.help.text": "You receive messages from {siteName} and course teams at this address.",
|
||||
"account.settings.field.secondary.email": "Recovery email address",
|
||||
"account.settings.field.secondary.email.empty": "Add a recovery email address",
|
||||
"account.settings.field.secondary.email.confirmation": "We’ve sent a confirmation message to {value}. Click the link in the message to update your recovery email address.",
|
||||
"account.settings.email.field.confirmation.header": "Pending confirmation",
|
||||
"account.settings.field.dob": "Year of birth",
|
||||
"account.settings.field.dob.empty": "Add year of birth",
|
||||
"account.settings.field.year_of_birth.options.empty": "Select a year of birth",
|
||||
"account.settings.field.dob.month": "Month",
|
||||
"account.settings.field.dob.year": "Year",
|
||||
"account.settings.field.month.year.default": "Select month",
|
||||
"account.settings.field.dob.year.default": "Select year",
|
||||
"account.settings.field.dob.form.button": "Please confirm your date of birth",
|
||||
"account.settings.field.dob.form.title": "Enter your birth month and year",
|
||||
"account.settings.field.dob.form.help.text": "We ask for birth month and year information to help us comply with our legal obligations.",
|
||||
"account.settings.field.dob.form.success": "Thank you for entering your information.",
|
||||
"account.settings.field.month_of_birth.options.empty": "Select a month of birth",
|
||||
"account.settingsfield.dob.error.general": "A technical error occurred. Please try again.",
|
||||
"account.settings.field.country": "Country",
|
||||
"account.settings.field.country.empty": "Add country",
|
||||
"account.settings.field.country.options.empty": "Select a Country",
|
||||
"account.settings.field.state": "State",
|
||||
"account.settings.field.state.empty": "Add state",
|
||||
"account.settings.field.state.options.empty": "Select a State",
|
||||
"account.settings.field.site.language": "Site language",
|
||||
"account.settings.field.site.language.help.text": "The language used throughout this site. This site is currently available in a limited number of languages.",
|
||||
"account.settings.field.education": "Education",
|
||||
"account.settings.field.education.empty": "Add level of education",
|
||||
"account.settings.field.education.levels.empty": "Select a level of education",
|
||||
"account.settings.field.education.levels.p": "Doctorate",
|
||||
"account.settings.field.education.levels.m": "Master's or professional degree",
|
||||
"account.settings.field.education.levels.b": "Bachelor's Degree",
|
||||
"account.settings.field.education.levels.a": "Associate's degree",
|
||||
"account.settings.field.education.levels.hs": "Secondary/high school",
|
||||
"account.settings.field.education.levels.jhs": "Junior secondary/junior high/middle school",
|
||||
"account.settings.field.education.levels.el": "Elementary/primary school",
|
||||
"account.settings.field.education.levels.none": "No formal education",
|
||||
"account.settings.field.education.levels.other": "Other education",
|
||||
"account.settings.field.gender": "Gender",
|
||||
"account.settings.field.gender.empty": "Add gender",
|
||||
"account.settings.field.gender.options.empty": "Select a gender",
|
||||
"account.settings.field.gender.options.f": "Female",
|
||||
"account.settings.field.gender.options.m": "Male",
|
||||
"account.settings.field.gender.options.o": "Other",
|
||||
"account.settings.field.language.proficiencies": "Spoken language",
|
||||
"account.settings.field.language.proficiencies.empty": "Add a spoken language",
|
||||
"account.settings.field.language_proficiencies.options.empty": "Select a Language",
|
||||
"account.settings.field.time.zone": "Time zone",
|
||||
"account.settings.field.time.zone.empty": "Set time zone",
|
||||
"account.settings.field.time.zone.description": "Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser’s local time zone.",
|
||||
"account.settings.field.time.zone.default": "Default (Local Time Zone)",
|
||||
"account.settings.field.time.zone.all": "All time zones",
|
||||
"account.settings.field.time.zone.country": "Country time zones",
|
||||
"account.settings.section.social.media": "Social Media Links",
|
||||
"account.settings.section.social.media.description": "Optionally, link your personal accounts to the social media icons on your {siteName} profile.",
|
||||
"account.settings.field.social.platform.name.linkedin": "LinkedIn",
|
||||
"account.settings.field.social.platform.name.linkedin.empty": "Add LinkedIn profile",
|
||||
"account.settings.jump.nav.delete.account": "Delete My Account",
|
||||
"account.settings.field.social.platform.name.twitter": "Twitter",
|
||||
"account.settings.field.social.platform.name.twitter.empty": "Add Twitter profile",
|
||||
"account.settings.field.social.platform.name.facebook": "Facebook",
|
||||
"account.settings.field.social.platform.name.facebook.empty": "Add Facebook profile",
|
||||
"account.settings.editable.field.action.save": "Save",
|
||||
"account.settings.editable.field.action.cancel": "Cancel",
|
||||
"account.settings.editable.field.action.edit": "Edit",
|
||||
"account.settings.static.field.empty": "No value set. Contact your {enterprise} administrator to make changes.",
|
||||
"account.settings.static.field.empty.no.admin": "No value set.",
|
||||
"notification.preferences.notifications.label": "Notifications",
|
||||
"account.settings.work.experience": "Work Experience",
|
||||
"account.settings.field.work.experience.empty": "Add work experience",
|
||||
"account.settings.field.work.experience.options.empty": "Select work experience",
|
||||
"error.notfound.message": "The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again.",
|
||||
"account.page.title": "Account | {siteName}",
|
||||
"id.verification.access.blocked.denied": "We cannot verify your identity at this time. If you have yet to activate your account, please check your spam folder for the activation email from {email}.",
|
||||
"id.verification.next": "Next",
|
||||
"id.verification.support": "support",
|
||||
"id.verification.example.card.alt": "Example of a valid identification card with a full name and photo.",
|
||||
"id.verification.requirements.title": "Photo Verification Requirements",
|
||||
"id.verification.requirements.description": "In order to complete Photo Verification, you will need the following:",
|
||||
"id.verification.requirements.card.device.title": "Device with Camera",
|
||||
"id.verification.requirements.card.device.allow": "Allow",
|
||||
"id.verification.requirements.card.id.title": "Photo Identification Card",
|
||||
"id.verification.requirements.card.id.text": "You need a valid identification card that contains your full name and photo, such as a driver’s license or passport.",
|
||||
"id.verification.privacy.title": "Privacy Information",
|
||||
"id.verification.privacy.need.photo.question": "Why does {siteName} need my photo?",
|
||||
"id.verification.privacy.need.photo.answer": "We use your verification photos to confirm your identity and ensure the validity of your certificate.",
|
||||
"id.verification.privacy.do.with.photo.question": "What does {siteName} do with this photo?",
|
||||
"id.verification.privacy.do.with.photo.answer": "We securely encrypt your photo and send it our authorization service for review. Your photo and information are not saved or visible anywhere on {siteName} after the verification process is complete.",
|
||||
"id.verification.access.blocked.title": "Identity Verification",
|
||||
"id.verification.access.blocked.enrollment": "You are not currently enrolled in a course that requires identity verification.",
|
||||
"id.verification.access.blocked.pending": "You have already submitted your verification information. You will see a message on your dashboard when the verification process is complete (usually within 5 days).",
|
||||
"id.verification.photo.take": "Take Photo",
|
||||
"id.verification.photo.retake": "Retake Photo?",
|
||||
"id.verification.photo.enable.detection": "Enable Face Detection",
|
||||
"id.verification.photo.enable.detection.portrait.help.text": "If checked, a box will appear around your face. Your face can be seen clearly if the box around it is blue. If your face is not in a good position or undetectable, the box will be red.",
|
||||
"id.verification.photo.enable.detection.id.help.text": "If checked, a box will appear around the face on your ID card. The face can be seen clearly if the box around it is blue. If the face is not in a good position or undetectable, the box will be red.",
|
||||
"id.verification.photo.feedback.correct": "Face is in a good position.",
|
||||
"id.verification.photo.feedback.two.faces": "More than one face detected.",
|
||||
"id.verification.photo.feedback.no.faces": "No face detected.",
|
||||
"id.verification.photo.feedback.top.left": "Incorrect position. Top left.",
|
||||
"id.verification.photo.feedback.top.center": "Incorrect position. Top center.",
|
||||
"id.verification.photo.feedback.top.right": "Incorrect position. Top right.",
|
||||
"id.verification.photo.feedback.center.left": "Incorrect position. Center left.",
|
||||
"id.verification.photo.feedback.center.center": "Incorrect position. Too close to camera.",
|
||||
"id.verification.photo.feedback.center.right": "Incorrect position. Center right.",
|
||||
"id.verification.photo.feedback.bottom.left": "Incorrect position. Bottom left.",
|
||||
"id.verification.photo.feedback.bottom.center": "Incorrect position. Bottom center.",
|
||||
"id.verification.photo.feedback.bottom.right": "Incorrect position. Bottom right.",
|
||||
"id.verification.camera.access.title": "Camera Permissions",
|
||||
"id.verification.camera.access.title.success": "Camera Access Enabled",
|
||||
"id.verification.camera.access.title.failed": "Camera Access Failed",
|
||||
"id.verification.camera.access.click.allow": "Please make sure to click \"Allow\"",
|
||||
"id.verification.camera.access.enable": "Enable Camera",
|
||||
"id.verification.camera.access.problems": "Having problems?",
|
||||
"id.verification.camera.access.skip": "Skip and upload image files instead",
|
||||
"id.verification.camera.access.success": "Looks like your camera is working and ready.",
|
||||
"id.verification.camera.access.failure": "It looks like we're unable to access your camera. You will need to upload image files of you and your photo id.",
|
||||
"id.verification.camera.access.failure.temporary": "It looks like we're unable to access your camera. Please verify that your webcam is connected and that you have allowed your browser to access it.",
|
||||
"id.verification.camera.access.failure.temporary.chrome": "To enable camera access in Chrome:",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step1": "Open Chrome.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2": "Navigate to More > Settings.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "For Windows: Alt+F, Alt+E, or F10 followed by the spacebar",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "For Mac: Command+,",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step3": "Under the \"Privacy and security\" tab, select \"Site Settings\" and then \"Camera.\"",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step4": "Under \"Blocked,\" find \"edx.org\" and select it.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step5": "In the \"Permissions\" section, update the camera permissions to \"Allow.\"",
|
||||
"id.verification.camera.access.failure.temporary.ie11": "To enable camera access in Internet Explorer:",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step1": "Open the Flash Player Settings Manager by navigating to Windows Settings > Control Panel > Flash Player.",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step2": "Select the \"Camera and Mic\" tab, and then select the \"Camera and Microphone Settings by Site\" button.",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step3": "Choose \"edx.org\" from the list of websites and change the permissions by selecting \"Allow\" in the dropdown menu.",
|
||||
"id.verification.camera.access.failure.temporary.firefox": "To enable camera access in Firefox:",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step1": "Open Firefox.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step2": "Enter \"about:preferences\" in the URL bar.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step3": "Select the \"Privacy & Security\" tab, and navigate to the \"Permissions\" section.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step4": "Next to \"Camera,\" select the \"Settings…\" button.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step5": "In the search bar, enter \"edx.org.\"",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step6": "In the status column for \"edx.org,\" select \"Allow\" from the drop down.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step7": "Select \"Save Changes.\"",
|
||||
"id.verification.camera.access.failure.temporary.safari": "To enable camera access in Safari:",
|
||||
"id.verification.camera.access.failure.temporary.safari.step1": "Open Safari.",
|
||||
"id.verification.camera.access.failure.temporary.safari.step2": "Click on the Safari app menu, then select \"Preferences.\" You can also use Command+, as a keyboard shortcut.",
|
||||
"id.verification.camera.access.failure.temporary.safari.step3": "Select the \"Websites\" tab and then select \"Camera.\"",
|
||||
"id.verification.camera.access.failure.temporary.safari.step4": "Select \"edx.org\" and change the camera permissions to \"Allow.\"",
|
||||
"id.verification.camera.access.failure.unsupported": "It looks like your browser does not support camera access.",
|
||||
"id.verification.camera.access.failure.unsupported.chrome.explanation": "The Chrome browser currently does not support camera access on iOS devices, such as iPhones and iPads.",
|
||||
"id.verification.camera.access.failure.unsupported.instructions": "Please use another browser to complete Identity Verification.",
|
||||
"id.verification.photo.tips.title": "Helpful Photo Tips",
|
||||
"id.verification.photo.tips.description": "Next, we'll need you to take a photo of your face. Please review the helpful tips below.",
|
||||
"id.verification.photo.tips.list.title": "Photo Tips",
|
||||
"id.verification.photo.tips.list.description": "To take a successful photo, make sure that:",
|
||||
"id.verification.photo.tips.list.well.lit": "Your face is well-lit.",
|
||||
"id.verification.photo.tips.list.inside.frame": "Your entire face fits inside the frame.",
|
||||
"id.verification.portrait.photo.title.camera": "Take a Photo of Yourself",
|
||||
"id.verification.portrait.photo.instructions.camera": "When your face is in position, use the Take Photo button below to take your photo.",
|
||||
"id.verification.camera.help.sight.question": "What if I can't see the camera image or if I can't see my photo to determine which side is visible?",
|
||||
"id.verification.camera.help.sight.answer.portrait": "You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally the best position for a headshot is approximately 12-18 inches (30-45 centimeters) from the camera, with your head centered relative to the computer screen. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle.",
|
||||
"id.verification.camera.help.sight.answer.id": "You may be able to complete the image capture procedure without assistance, but it may take a couple of submission attempts to get the camera positioning right. Optimal camera positioning varies with each computer, but generally, the best position for a photo of an ID card is 8-12 inches (20-30 centimeters) from the camera, with the ID card centered relative to the camera. If the photos you submit are rejected, try moving the computer or camera orientation to change the lighting angle. The most common reason for rejection is inability to read the text on the ID card.",
|
||||
"id.verification.camera.help.difficulty.question.portrait": "What if I have difficulty holding my head in position relative to the camera?",
|
||||
"id.verification.camera.help.difficulty.question.id": "What if I have difficulty holding my ID in position relative to the camera?",
|
||||
"id.verification.camera.help.difficulty.answer": "If you require assistance with taking a photo for submission, contact {siteName} support for additional suggestions.",
|
||||
"id.verification.id.photo.unclear.question": "Is your ID card image not clear or too blurry?",
|
||||
"id.verification.id.tips.title": "Helpful Identification Card Tips",
|
||||
"id.verification.id.tips.description": "Next, we'll need you to take a photo of a valid identification card that includes your full name and photo, such as a driver’s license or passport. Please have your ID ready.",
|
||||
"id.verification.id.tips.list.well.lit": "Your identification card is well-lit.",
|
||||
"id.verification.id.tips.list.clear": "Ensure that you can see your photo and clearly read your name.",
|
||||
"id.verification.id.photo.title.camera": "Take a Photo of Your Identification Card",
|
||||
"id.verification.id.photo.title.upload": "Upload a Photo of Your Identification Card",
|
||||
"id.verification.id.photo.preview.alt": "Preview of photo ID.",
|
||||
"id.verification.id.photo.instructions.camera": "When your ID is in position, use the Take Photo button below to take your photo. Please use a passport, driver’s license, or another identification card that includes your full name and a picture of your face.",
|
||||
"id.verification.id.photo.instructions.upload": "Please upload a photo of your identification card. Ensure the entire ID fits inside the frame and is well-lit. The file size must be under 10 MB. Supported formats:",
|
||||
"id.verification.id.photo.instructions.upload.error.invalidFileType": "The file you have selected is not a supported image type. Please choose from the following formats:",
|
||||
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "The file you have selected is too large. Please try again with a file less than 10MB.",
|
||||
"id.verification.name.check.title": "Double-Check Your Name",
|
||||
"id.verification.name.check.instructions": "Does the name below match the name on your photo ID? If not, update the name below to match your photo ID.",
|
||||
"id.verification.name.check.mismatch.information": "If the name below does not match your photo ID, your identity verification will be denied.",
|
||||
"id.verification.name.error": "Please enter your name as it appears on your photo ID.",
|
||||
"id.verification.account.name.warning.prefix": "Please Note:",
|
||||
"id.verification.account.name.settings": "Account Settings",
|
||||
"id.verification.name.label": "Name",
|
||||
"id.verification.account.name.photo.alt": "Photo of your ID to be submitted.",
|
||||
"id.verification.review.title": "Review Your Photos",
|
||||
"id.verification.review.description": "Make sure we can verify your identity with the photos and information you have provided.",
|
||||
"id.verification.review.portrait.label": "Your Portrait",
|
||||
"id.verification.review.portrait.alt": "Photo of your face to be submitted.",
|
||||
"id.verification.review.portrait.retake": "Retake Portrait Photo",
|
||||
"id.verification.review.id.label": "Your Identification Card",
|
||||
"id.verification.review.id.alt": "Photo of your identification card to be submitted.",
|
||||
"id.verification.review.id.retake": "Retake ID Photo",
|
||||
"id.verification.review.confirm": "Submit",
|
||||
"id.verification.submission.alert.error.face": "A photo of your face is required. Please retake your portrait photo.",
|
||||
"id.verification.submission.alert.error.id": "A photo of your ID card is required. Please retake your ID photo.",
|
||||
"id.verification.submission.alert.error.name": "A valid account name is required. Please update your account name to match the name on your ID.",
|
||||
"id.verification.submission.alert.error.unsupported": "One or more of the files you have uploaded is in an unsupported format. Please choose from the following:",
|
||||
"id.verification.review.error": "{siteName} Support Page",
|
||||
"id.verification.submitted.title": "Identity Verification in Progress",
|
||||
"id.verification.submitted.text": "We have received your information and are verifying your identity. You will be notified when the verification process is complete (usually within 5 days). In the meantime, you can still access all available course content.",
|
||||
"id.verification.return.dashboard": "Return to Your Dashboard",
|
||||
"id.verification.return.course": "Return to Course",
|
||||
"id.verification.return.generic": "Return",
|
||||
"id.verification.photo.upload.help.title": "Upload a Photo Instead",
|
||||
"id.verification.photo.camera.help.title": "Use Your Camera Instead",
|
||||
"id.verification.photo.upload.help.text": "If you are having trouble using the photo capture above, you may want to upload a photo instead. To upload a photo, click the button below.",
|
||||
"id.verification.photo.camera.help.text": "If you are having trouble uploading a photo above, you may want to use your camera instead. To use your camera, click the button below.",
|
||||
"id.verification.upload.help.button": "Switch to Upload Mode",
|
||||
"id.verification.camera.help.button": "Switch to Camera Mode",
|
||||
"notification.preference.heading": "Notifications",
|
||||
"notification.preference.app.title": "{ key, select, discussion {Discussions} coursework {Course Work} other {{key}} }",
|
||||
"notification.preference.title": "{ text, select, core {Core notifications} newDiscussionPost {New discussion posts} newQuestionPost {New question posts} other {{text}} }",
|
||||
"notification.preference.type.label": "Type",
|
||||
"notification.preference.web.label": "Web",
|
||||
"notification.preference.help.email": "Email",
|
||||
"notification.preference.help.push": "Push",
|
||||
"notification.preference.load.more.courses": "Load more courses",
|
||||
"notification.preference.guide.link": "as detailed here",
|
||||
"notification.preference.guide.body": "Notifications for certain activities are enabled by default,",
|
||||
"account.settings.field.name.certificate.select": "If checked, this name will appear on your certificates and public-facing records.",
|
||||
"account.settings.field.name.modal.certificate.title": "Choose a preferred name for certificates and public-facing records",
|
||||
"account.settings.field.name.modal.certificate.select": "Select a name",
|
||||
"account.settings.field.name.modal.certificate.option.full": "Full Name",
|
||||
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
|
||||
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
|
||||
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
|
||||
"account.settings.delete.account.text.3.edX": "You may also lose access to verified certificates and other program credentials like MicroMasters certificates. You can make a copy of these for your records before proceeding with deletion. {actionLink}.",
|
||||
"account.settings.delete.account.text.3": "You may also lose access to verified certificates and other program credentials. You can make a copy of these for your records before proceeding with deletion.",
|
||||
"account.settings.delete.account.header": "Delete My Account",
|
||||
"account.settings.delete.account.subheader": "We're sorry to see you go!",
|
||||
"account.settings.delete.account.text.1": "Please note: Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
|
||||
"account.settings.delete.account.text.2": "Once your account is deleted, you cannot use it to take courses on {siteName}.",
|
||||
"account.settings.delete.account.text.2.edX": "Once your account is deleted, you cannot use it to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer’s or university’s system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
|
||||
"account.settings.delete.account.text.3.link": "Follow these instructions for printing or downloading a certificate",
|
||||
"account.settings.delete.account.text.warning": "Warning: Account deletion is permanent. Please read the above carefully before proceeding. This is an irreversible action, and you will no longer be able to use the same email on {siteName}.",
|
||||
"account.settings.delete.account.text.change.instead": "Want to change your email, name, or password instead?",
|
||||
"account.settings.delete.account.button": "Delete My Account",
|
||||
"account.settings.delete.account.please.activate": "activate your account",
|
||||
"account.settings.delete.account.please.confirm": "confirm your account",
|
||||
"account.settings.delete.account.please.unlink": "unlink all social media accounts",
|
||||
"account.settings.delete.account.modal.header": "Are you sure?",
|
||||
"account.settings.delete.account.modal.text.1": "You have selected \"Delete My Account\". Deletion of your account and personal data is permanent and cannot be undone. {siteName} will not be able to recover your account or the data that is deleted.",
|
||||
"account.settings.delete.account.modal.text.2": "If you proceed, you will be unable to use this account to take courses on {siteName}.",
|
||||
"account.settings.delete.account.modal.text.2.edX": "If you proceed, you will be unable to use this account to take courses on the edX app, edx.org, or any other site hosted by edX. This includes access to edx.org from your employer's or university's system and access to private sites offered by MIT Open Learning, Wharton Executive Education, and Harvard Medical School.",
|
||||
"account.settings.delete.account.modal.enter.password": "If you still wish to continue and delete your account, please enter your account password:",
|
||||
"account.settings.delete.account.modal.confirm.delete": "Yes, Delete",
|
||||
"account.settings.delete.account.modal.confirm.cancel": "Cancel",
|
||||
"account.settings.delete.account.error.unable.to.delete": "Unable to delete account",
|
||||
"account.settings.delete.account.error.no.password": "A password is required",
|
||||
"account.settings.delete.account.error.invalid.password": "Password is incorrect",
|
||||
"account.settings.delete.account.error.unable.to.delete.details": "Sorry, there was an error trying to process your request. Please try again later.",
|
||||
"account.settings.delete.account.modal.after.header": "We're sorry to see you go! Your account will be deleted shortly.",
|
||||
"account.settings.delete.account.modal.after.text": "Account deletion, including removal from email lists, may take a few weeks to fully process through our system. If you want to opt-out of emails before then, please unsubscribe from the footer of any email.",
|
||||
"account.settings.delete.account.modal.after.button": "Close",
|
||||
"account.settings.message.demographics.service.issue": "An error occurred attempting to retrieve or save your account information. Please try again later.",
|
||||
"account.settings.field.demographics.gender": "Gender identity",
|
||||
"account.settings.field.demographics.gender.empty": "Add gender identity",
|
||||
"account.settings.field.demographics.gender.options.empty": "Select a gender identity",
|
||||
"account.settings.field.demographics.gender_description": "Gender identity description",
|
||||
"account.settings.field.demographics.gender_description.empty": "Enter description",
|
||||
"account.settings.field.demographics.ethnicity": "Race/Ethnicity identity",
|
||||
"account.settings.field.demographics.ethnicity.empty": "Add race/ethnicity identity",
|
||||
"account.settings.field.demographics.ethnicity.options.empty": "Select all that apply",
|
||||
"account.settings.field.demographics.income": "Family income",
|
||||
"account.settings.field.demographics.income.empty": "Add family income",
|
||||
"account.settings.field.demographics.income.options.empty": "Select a family income range",
|
||||
"account.settings.field.demographics.military_history": "U.S. Military status",
|
||||
"account.settings.field.demographics.military_history.empty": "Add military status",
|
||||
"account.settings.field.demographics.military_history.options.empty": "Select military status",
|
||||
"account.settings.field.demographics.learner_education_level": "Your education level",
|
||||
"account.settings.field.demographics.learner_education_level.empty": "Add education level",
|
||||
"account.settings.field.demographics.parent_education_level": "Parents/Guardians education level",
|
||||
"account.settings.field.demographics.parent_education_level.empty": "Add education level",
|
||||
"account.settings.field.demographics.education_level.options.empty": "Select education level",
|
||||
"account.settings.field.demographics.work_status": "Employment status",
|
||||
"account.settings.field.demographics.work_status.empty": "Add employment status",
|
||||
"account.settings.field.demographics.work_status.options.empty": "Select employment status",
|
||||
"account.settings.field.demographics.work_status_description": "Employment status description",
|
||||
"account.settings.field.demographics.work_status_description.empty": "Enter description",
|
||||
"account.settings.field.demographics.current_work_sector": "Current work industry",
|
||||
"account.settings.field.demographics.current_work_sector.empty": "Add work industry",
|
||||
"account.settings.field.demographics.future_work_sector": "Future work industry",
|
||||
"account.settings.field.demographics.future_work_sector.empty": "Add work industry",
|
||||
"account.settings.field.demographics.work_sector.options.empty": "Select work industry",
|
||||
"account.settings.section.demographics.why": "Why does {siteName} collect this information?",
|
||||
"account.settings.name.change.title.id": "This name change requires identity verification",
|
||||
"account.settings.name.change.title.begin": "Before we begin",
|
||||
"account.settings.name.change.warning.one": "Warning: This action updates the name that appears on all certificates that have been earned on this account in the past and any certificates you are currently earning or will earn in the future.",
|
||||
"account.settings.name.change.warning.two": "This action cannot be undone without verifying your identity.",
|
||||
"account.settings.name.change.id.name.label": "Enter your name as it appears on your unexpired student, work, or government-issued identification card.",
|
||||
"account.settings.name.change.id.name.placeholder": "Enter the name on your photo ID",
|
||||
"account.settings.name.change.error.valid.name": "Please enter a valid name.",
|
||||
"account.settings.name.change.error.general": "A technical error occurred. Please try again.",
|
||||
"account.settings.name.change.continue": "Continue",
|
||||
"account.settings.name.change.cancel": "Cancel",
|
||||
"account.settings.editable.field.password.reset.button.confirmation.support.link": "technical support",
|
||||
"account.settings.editable.field.password.reset.button.confirmation": "We've sent a message to {email}. Click the link in the message to reset your password. Didn't receive the message? Contact {technicalSupportLink}.",
|
||||
"account.settings.editable.field.password.reset.button.forbidden": "Your previous request is in progress, please try again in few moments.",
|
||||
"account.settings.editable.field.password.reset.label": "Password",
|
||||
"account.settings.editable.field.password.reset.button": "Reset Password",
|
||||
"account.settings.sso.link.account": "Sign in with {name}",
|
||||
"account.settings.sso.account.connected": "Linked",
|
||||
"account.settings.sso.account.disconnect.error": "There was a problem disconnecting this account. Contact support if the problem persists.",
|
||||
"account.settings.sso.unlink.account": "Unlink {name} account",
|
||||
"account.settings.sso.no.providers": "No accounts can be linked at this time.",
|
||||
"id.verification.request.camera.access.instructions": "In order to take a photo using your webcam, you may receive a browser prompt for access to your camera. {clickAllow}",
|
||||
"id.verification.requirements.account.managed.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help before completing the Photo Verification process.",
|
||||
"id.verification.requirements.card.device.text": "You need a device that has a camera. If you receive a browser prompt for access to your camera, please make sure to click {allow}.",
|
||||
"id.verification.account.name.summary.alert": "Your account settings are managed by {managerTitle}. If the name on your photo ID does not match the name on your account, please contact your {profileDataManager} administrator or {support} for help.",
|
||||
"idv.submission.alert.error": "We encountered a technical error while trying to submit ID verification. This might be a temporary issue, so please try again in a few minutes. If the problem persists, please go to {support_link} for help.",
|
||||
"id.verification.account.name.edit": "Edit {sr}"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user