Compare commits
522 Commits
open-relea
...
renovate/h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea96f51805 | ||
|
|
a5c3aa926d | ||
|
|
e3c9bf5451 | ||
|
|
a979c3e6d3 | ||
|
|
304bde12b4 | ||
|
|
3b551ab87f | ||
|
|
a27c24b2f7 | ||
|
|
8c4143f8a5 | ||
|
|
e9f6acff27 | ||
|
|
dc26da9cc1 | ||
|
|
79b590f739 | ||
|
|
9dd05761fd | ||
|
|
a3a817e51a | ||
|
|
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 | ||
|
|
51b758a18f | ||
|
|
30bd145bdd | ||
|
|
1f15802bc6 | ||
|
|
5ba476a570 | ||
|
|
dfb13c4286 | ||
|
|
aada46f6eb | ||
|
|
8c57140640 | ||
|
|
9e967ba1ea | ||
|
|
8239209cd7 | ||
|
|
65bb042443 | ||
|
|
4dd7529799 | ||
|
|
e6684f5048 | ||
|
|
e276d6a5c4 | ||
|
|
36f1d1dbfb | ||
|
|
6c324e85fc | ||
|
|
16b5d066ff | ||
|
|
6e45abbe8b | ||
|
|
b3e6335396 | ||
|
|
08a2da5459 | ||
|
|
094361c689 | ||
|
|
b7b33ef597 | ||
|
|
afa808ff5d | ||
|
|
5279f2a9c9 | ||
|
|
4cc00fd7e3 | ||
|
|
8815626411 | ||
|
|
db319b6cdf | ||
|
|
50edcb1c50 | ||
|
|
d6519bc825 | ||
|
|
b54aeb9446 | ||
|
|
bb1bd6e648 | ||
|
|
7df9f92dd8 | ||
|
|
3627915985 | ||
|
|
9fe1a04a0a | ||
|
|
7455821500 | ||
|
|
817980be00 | ||
|
|
7d4e31f69d | ||
|
|
34dde09ccc | ||
|
|
aee4e44f8c | ||
|
|
7381cfd3b6 | ||
|
|
1d0bd3986c | ||
|
|
1c0dc36907 | ||
|
|
ac0ab9daea | ||
|
|
0d45d17cd3 | ||
|
|
a6d265b885 | ||
|
|
3508bc6c34 | ||
|
|
28de621fc7 | ||
|
|
e6df5e77ae | ||
|
|
8bc5c1fae8 | ||
|
|
6e48c9d2d1 | ||
|
|
d3469d648f | ||
|
|
cc65ffc96f | ||
|
|
5640fb95c2 | ||
|
|
7bbb889258 | ||
|
|
53b59231cb | ||
|
|
8fb25fd89b | ||
|
|
15d2bf60f9 | ||
|
|
135826bc52 | ||
|
|
d7251e6aec | ||
|
|
0b0846fb00 | ||
|
|
b1cd1b1995 | ||
|
|
50c468857a | ||
|
|
c940d3463c | ||
|
|
376deba866 | ||
|
|
37d0e6e0fb | ||
|
|
9261711d4a | ||
|
|
3e42d42ad7 | ||
|
|
7de4edc002 | ||
|
|
2936498b02 | ||
|
|
44d26c444b | ||
|
|
b1c1c6502d | ||
|
|
21dda3f25b | ||
|
|
f87b5040a3 | ||
|
|
0dc1df07d4 | ||
|
|
efa682092f | ||
|
|
0d9e6f8b87 | ||
|
|
26d2b50859 | ||
|
|
3eb63cd624 | ||
|
|
ba0774c5c4 | ||
|
|
ac47d0b180 | ||
|
|
02038b8ac9 | ||
|
|
458f9f7e3d | ||
|
|
c7d9c270f9 | ||
|
|
d0ecbbfb8a | ||
|
|
22db0d9202 | ||
|
|
34a142f55f | ||
|
|
6e00915f98 | ||
|
|
4cfa1707de | ||
|
|
a721887886 | ||
|
|
9cdbb93bf3 | ||
|
|
f9e7519e26 | ||
|
|
ff8d5a4d09 | ||
|
|
f14c71c4fb | ||
|
|
43caac8430 | ||
|
|
ee1ecb8ab9 | ||
|
|
020aa84986 | ||
|
|
24459daf6d | ||
|
|
587533703e | ||
|
|
866746d1c6 | ||
|
|
4c618a55c0 | ||
|
|
c9f6cf708e | ||
|
|
5f314ee65f | ||
|
|
7e35b23b36 | ||
|
|
842bd11d89 | ||
|
|
b1e11dfb36 | ||
|
|
aa57b69924 | ||
|
|
e7769b37e9 | ||
|
|
fcc7b26c28 | ||
|
|
16d844528d | ||
|
|
223234f623 | ||
|
|
d8a1c0ca8c | ||
|
|
1da8f630eb | ||
|
|
228eec0afa | ||
|
|
cf62b4b82c | ||
|
|
0184c1fa25 | ||
|
|
8ed103b2ad | ||
|
|
84a9de44a5 | ||
|
|
84df0a0b3e | ||
|
|
a3917ae550 | ||
|
|
bfd6a07a2c | ||
|
|
1df570989b | ||
|
|
b7e433876e | ||
|
|
1669d577f6 | ||
|
|
d1ca7decce | ||
|
|
79a43ae713 | ||
|
|
9d0b315714 | ||
|
|
4b2bc11378 | ||
|
|
fc7ce6b91e | ||
|
|
39a25fe5bc | ||
|
|
307cb1541b | ||
|
|
03e026ce4e | ||
|
|
a29876aff0 | ||
|
|
ab77246015 | ||
|
|
572b05e7f1 | ||
|
|
3e4de47ba6 | ||
|
|
6c6cedd422 | ||
|
|
43694921ca | ||
|
|
06ded1e66e | ||
|
|
aba1bb3382 | ||
|
|
d67b880028 | ||
|
|
29692add53 | ||
|
|
7f53bf32ca | ||
|
|
add22d9756 | ||
|
|
bef9bf76fd | ||
|
|
bf6b2fb8b8 | ||
|
|
a77cd6d91a | ||
|
|
9b3f222191 | ||
|
|
90f2ed8393 | ||
|
|
95c53ad380 | ||
|
|
3023cd3d55 | ||
|
|
50f85674b1 | ||
|
|
9437ee36f3 | ||
|
|
463944012c | ||
|
|
2d297aa7be | ||
|
|
1ab5901d24 | ||
|
|
ba678d92f7 | ||
|
|
884651a702 | ||
|
|
a152c631da | ||
|
|
fbe91ce7e4 | ||
|
|
13681c1360 | ||
|
|
43435f8ff3 | ||
|
|
830fb05819 | ||
|
|
f62bd5ad76 | ||
|
|
e2f9edd623 | ||
|
|
0b63613736 | ||
|
|
940828ff18 | ||
|
|
296f68f7dd | ||
|
|
e59ada660c | ||
|
|
c123daacd6 | ||
|
|
7440cd367f | ||
|
|
1d639c4a57 | ||
|
|
6db789d6ac | ||
|
|
4bbff91ad7 | ||
|
|
24e32cd0c5 | ||
|
|
d1548b7287 | ||
|
|
d1de13469f | ||
|
|
62418db3ba | ||
|
|
24f70972d8 | ||
|
|
a7c783888f | ||
|
|
00b1bf58a5 | ||
|
|
b2c08193f7 | ||
|
|
2bbe7dc3f1 | ||
|
|
4d93fd6c0f | ||
|
|
613229bbd1 | ||
|
|
39db13435f | ||
|
|
994f22ab41 | ||
|
|
579654c092 | ||
|
|
6094509679 | ||
|
|
82170f383f | ||
|
|
addc3640cc | ||
|
|
b9c0069117 | ||
|
|
11c31ba216 | ||
|
|
ccd2a5c074 | ||
|
|
ff2e86fa13 | ||
|
|
f71033232e | ||
|
|
1ff20786ff | ||
|
|
232bead0c9 | ||
|
|
bd0c879a51 | ||
|
|
a52e1ed2a5 | ||
|
|
b8356bc962 | ||
|
|
240e589310 | ||
|
|
152d3230d1 | ||
|
|
95bd1cc66f | ||
|
|
5db6d2b319 | ||
|
|
368bc00321 | ||
|
|
fa4c5ef872 | ||
|
|
01f7180bb9 | ||
|
|
ce79213b21 | ||
|
|
3870732da8 | ||
|
|
60d7a469a7 | ||
|
|
921107354c | ||
|
|
9ea6120ded | ||
|
|
31493af69a | ||
|
|
732cf11d96 | ||
|
|
42c1c49a85 | ||
|
|
976652539c | ||
|
|
1e0b273579 | ||
|
|
5995434b97 | ||
|
|
2a854d53b6 | ||
|
|
695a9e4297 | ||
|
|
8a79046f66 | ||
|
|
27b2d3f853 | ||
|
|
11a6d6b0ee | ||
|
|
0fd470ad5e | ||
|
|
251c799e18 | ||
|
|
69b902081e | ||
|
|
b30012fd7b | ||
|
|
8dcf228faa | ||
|
|
3db3f87375 | ||
|
|
6b51999890 | ||
|
|
65b6b30f01 | ||
|
|
00be4845b8 | ||
|
|
4a3270d0a8 | ||
|
|
fa91f25ad3 | ||
|
|
f7bb06e109 | ||
|
|
340ec87522 | ||
|
|
9e6a74c633 | ||
|
|
7c05188e5f | ||
|
|
a19c37fa85 | ||
|
|
600a2b8fe2 | ||
|
|
402dbae44f | ||
|
|
75fddb9b69 | ||
|
|
6fff8630d4 | ||
|
|
06b4fc641e | ||
|
|
19d5411402 | ||
|
|
14160eaeb3 | ||
|
|
b06138f96d | ||
|
|
01d0b3645b | ||
|
|
0a4dcb4eaa | ||
|
|
37e338df0f | ||
|
|
6a0e27c816 | ||
|
|
289341dd91 | ||
|
|
45f9a15885 | ||
|
|
f982187be6 | ||
|
|
9d0d03c402 | ||
|
|
8602561e55 | ||
|
|
b310574f18 | ||
|
|
73badaf916 | ||
|
|
f02cc43078 | ||
|
|
bd5c2343be | ||
|
|
6349d487e4 | ||
|
|
9c6137e668 | ||
|
|
2cf01270d7 | ||
|
|
403df8926d | ||
|
|
4bd96c70af | ||
|
|
d2a835f560 | ||
|
|
d0b5d54d0a | ||
|
|
26299eed65 | ||
|
|
648bea8d84 | ||
|
|
7409f02056 | ||
|
|
f5dd409816 | ||
|
|
6ddac11dc0 | ||
|
|
7019aea4fb | ||
|
|
5424434599 | ||
|
|
8ca9dc78a9 | ||
|
|
30e25b96bb | ||
|
|
1d01abc7da | ||
|
|
917152df22 | ||
|
|
961c0feb78 | ||
|
|
7d57d86729 | ||
|
|
34c5de1340 | ||
|
|
81d604d046 | ||
|
|
e6f7e83cf5 | ||
|
|
a970e17070 | ||
|
|
f471ae0aa7 | ||
|
|
b9efe6faee | ||
|
|
2dbccec1f1 | ||
|
|
9f38b975d9 | ||
|
|
ae355cefcf | ||
|
|
d63dfc929f | ||
|
|
64be9edeac | ||
|
|
5f4f82eae1 | ||
|
|
c8c7352549 | ||
|
|
88206e4282 | ||
|
|
d8e23b1a02 | ||
|
|
5db21d2483 | ||
|
|
526d6114f2 | ||
|
|
0cc38e2dc6 | ||
|
|
380ca7c816 | ||
|
|
bcb20234ab | ||
|
|
87ff50ace8 | ||
|
|
b409aff6b4 | ||
|
|
2b67d037bf | ||
|
|
4137996a91 | ||
|
|
1319bd6377 | ||
|
|
a579e86e98 | ||
|
|
74bb2fb45f | ||
|
|
11dbbad20b | ||
|
|
a0227f1dbc | ||
|
|
f6a7a6063c | ||
|
|
0aa02687e6 | ||
|
|
303f6a5d3f | ||
|
|
b262f42c8d | ||
|
|
b92794b72c | ||
|
|
3f754fa114 | ||
|
|
bf274e5186 | ||
|
|
be9cf70c5c | ||
|
|
c00ea15920 | ||
|
|
5a8bd309e7 | ||
|
|
d83ea54272 | ||
|
|
eae18d9c63 | ||
|
|
a18da61cec | ||
|
|
80d5fd2a34 | ||
|
|
d4e9ba0420 | ||
|
|
5add376c31 | ||
|
|
559c9aa1a9 | ||
|
|
ea8a6d29d0 | ||
|
|
4689482137 | ||
|
|
d48be79e53 | ||
|
|
ed94cc68e3 | ||
|
|
05740b37ff | ||
|
|
b2c8164cd1 | ||
|
|
47b369f797 | ||
|
|
88239f2700 | ||
|
|
602c4b484c | ||
|
|
45ec573ff9 | ||
|
|
e0befb8b60 | ||
|
|
9f4d944670 | ||
|
|
60d26649dd | ||
|
|
55f25c73ca | ||
|
|
29dbdf6ad0 | ||
|
|
5bfa834563 | ||
|
|
5e3af50e3b | ||
|
|
e30a20b185 | ||
|
|
2743e05890 | ||
|
|
71c0563e3a | ||
|
|
5d8d327a48 | ||
|
|
15ba7c087e | ||
|
|
6df7ad243b | ||
|
|
594d3ff9f1 | ||
|
|
7bb9d09dae | ||
|
|
0e6233b693 | ||
|
|
22087d2d2c | ||
|
|
64798cdc80 | ||
|
|
f53ba967e5 | ||
|
|
2deb47d542 | ||
|
|
1b9dd3bdf5 | ||
|
|
fd3c8ede6d | ||
|
|
c1fdace72d | ||
|
|
4ca4d55796 | ||
|
|
2da606bf6f | ||
|
|
997a3c0b98 | ||
|
|
f627257a1c | ||
|
|
9aa2a816b4 | ||
|
|
fc54dd528f | ||
|
|
53fc1b325c | ||
|
|
48b02cd2de | ||
|
|
490274b2ed | ||
|
|
10dc9aabde | ||
|
|
74ec75781e | ||
|
|
c428d3044f | ||
|
|
d9777fe48e | ||
|
|
6b7ab05dd5 | ||
|
|
ce79cd7f5a | ||
|
|
b8ab0a2150 | ||
|
|
56569c717c | ||
|
|
45511860c2 | ||
|
|
11f7d56e75 | ||
|
|
f19380ebac | ||
|
|
5eb43871c7 | ||
|
|
a2388bffc2 | ||
|
|
ebe6af0913 | ||
|
|
fd6ba7847a | ||
|
|
f051905da1 | ||
|
|
5eddb35e0b | ||
|
|
0196245c13 | ||
|
|
0c53a29094 | ||
|
|
60643f6215 | ||
|
|
b0aa91fc98 | ||
|
|
09813fa689 | ||
|
|
b05a40d0aa | ||
|
|
1a76468587 | ||
|
|
93ef8d2b04 | ||
|
|
98efe2649a | ||
|
|
80cc8f156f | ||
|
|
1364ca5711 | ||
|
|
c337e03a4d |
49
.env
49
.env
@@ -1,17 +1,32 @@
|
||||
ACCESS_TOKEN_COOKIE_NAME=null
|
||||
BASE_URL=null
|
||||
CREDENTIALS_BASE_URL=null
|
||||
CSRF_TOKEN_API_PATH=null
|
||||
ECOMMERCE_BASE_URL=null
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME=null
|
||||
LMS_BASE_URL=null
|
||||
LOGIN_URL=null
|
||||
LOGOUT_URL=null
|
||||
MARKETING_SITE_BASE_URL=null
|
||||
NODE_ENV=null
|
||||
ORDER_HISTORY_URL=null
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT=null
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME=null
|
||||
SUPPORT_URL=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
ACCESS_TOKEN_COOKIE_NAME=''
|
||||
BASE_URL=''
|
||||
COACHING_ENABLED=''
|
||||
CREDENTIALS_BASE_URL=''
|
||||
CSRF_TOKEN_API_PATH=''
|
||||
DEMOGRAPHICS_BASE_URL=''
|
||||
DISCOVERY_API_BASE_URL=''
|
||||
ECOMMERCE_BASE_URL=''
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||
FAVICON_URL=''
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME=''
|
||||
LMS_BASE_URL=''
|
||||
LOGIN_URL=''
|
||||
LOGO_TRADEMARK_URL=''
|
||||
LOGO_URL=''
|
||||
LOGO_WHITE_URL=''
|
||||
LOGOUT_URL=''
|
||||
MARKETING_SITE_BASE_URL=''
|
||||
NODE_ENV=''
|
||||
ORDER_HISTORY_URL=''
|
||||
PUBLISHER_BASE_URL=''
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME=''
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL=''
|
||||
USER_INFO_COOKIE_NAME=''
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
|
||||
@@ -1,20 +1,34 @@
|
||||
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'
|
||||
DISCOVERY_API_BASE_URL=''
|
||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||
LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||
LOGOUT_URL='http://localhost:18000/logout'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:5335'
|
||||
NODE_ENV='development'
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
PORT=1997
|
||||
PUBLISHER_BASE_URL=''
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME=localhost
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
# Temporary, Remove this once we are ready to release the feature.
|
||||
COACHING_ENABLED=true
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
|
||||
|
||||
26
.env.test
26
.env.test
@@ -1,18 +1,32 @@
|
||||
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'
|
||||
DISCOVERY_API_BASE_URL=''
|
||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||
LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||
LOGOUT_URL='http://localhost:18000/logout'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
NODE_ENV=null
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:5335'
|
||||
NODE_ENV=''
|
||||
ORDER_HISTORY_URL='http://localhost:1996/orders'
|
||||
PUBLISHER_BASE_URL=''
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
SEGMENT_KEY=''
|
||||
SITE_NAME=localhost
|
||||
STUDIO_BASE_URL=''
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
COACHING_ENABLED=''
|
||||
ENABLE_COPPA_COMPLIANCE=''
|
||||
ENABLE_DOB_UPDATE=''
|
||||
MARKETING_EMAILS_OPT_IN=''
|
||||
APP_ID=
|
||||
MFE_CONFIG_API_URL=
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
|
||||
module.exports = createConfig('eslint');
|
||||
|
||||
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 }}
|
||||
30
.github/workflows/ci.yml
vendored
Normal file
30
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: ci
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16]
|
||||
npm: [8.5.0]
|
||||
npm-test:
|
||||
- i18n_extract
|
||||
- lint
|
||||
- test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install -g npm@${{ matrix.npm }}
|
||||
- run: make requirements
|
||||
- run: make test NPM_TESTS=build
|
||||
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
||||
- name: upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
fail_ci_if_error: false
|
||||
10
.github/workflows/commitlint.yml
vendored
Normal file
10
.github/workflows/commitlint.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Run commitlint on the commit messages in a pull request.
|
||||
|
||||
name: Lint Commit Messages
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
commitlint:
|
||||
uses: openedx/.github/.github/workflows/commitlint.yml@master
|
||||
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/lockfileversion-check.yml@master
|
||||
|
||||
41
.github/workflows/update-browserlist.yml
vendored
Normal file
41
.github/workflows/update-browserlist.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Update Browserlist DB
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-dep:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [16]
|
||||
npm: [8.5.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- run: npm install -g npm@${{ matrix.npm }}
|
||||
- run: make requirements
|
||||
|
||||
- name: Update dependencies
|
||||
run: npx browserslist@latest --update-db
|
||||
|
||||
- name: setup testeng-ci
|
||||
run: |
|
||||
git clone https://github.com/edx/testeng-ci.git
|
||||
cd $GITHUB_WORKSPACE/testeng-ci
|
||||
pip install -r requirements/base.txt
|
||||
- name: create pull request
|
||||
id: createpullrequest
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.requirements_bot_github_token }}
|
||||
GITHUB_USER_EMAIL: ${{ secrets.requirements_bot_github_email }}
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/testeng-ci
|
||||
python -m jenkins.pull_request_creator --repo-root=$GITHUB_WORKSPACE \
|
||||
--target-branch="master" --base-branch-name="update-browserslist" \
|
||||
--commit-message="chore: update browserslist" --pr-title="Update browserslist DB" \
|
||||
--pr-body="Updated browserlist DB" --delete-old-pull-requests --output-pr-url-for-github-action
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@ temp/babel-plugin-react-intl
|
||||
*~
|
||||
/temp
|
||||
/.vscode
|
||||
/module.config.js
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,15 +0,0 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
before_install:
|
||||
- npm install -g npm@6
|
||||
install:
|
||||
- npm ci
|
||||
script:
|
||||
- make validate-no-uncommitted-package-lock-changes
|
||||
- npm run i18n_extract
|
||||
- npm run lint
|
||||
- npm run test
|
||||
- npm run build
|
||||
- npm run is-es5
|
||||
after_success:
|
||||
- codecov
|
||||
@@ -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
|
||||
|
||||
|
||||
24
Makefile
24
Makefile
@@ -1,3 +1,4 @@
|
||||
export TRANSIFEX_RESOURCE = frontend-app-account
|
||||
transifex_resource = frontend-app-account
|
||||
transifex_langs = "ar,fr,es_419,zh_CN"
|
||||
|
||||
@@ -10,8 +11,19 @@ tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transi
|
||||
# This directory must match .babelrc .
|
||||
transifex_temp = ./temp/babel-plugin-react-intl
|
||||
|
||||
requirements:
|
||||
npm install
|
||||
NPM_TESTS=build i18n_extract lint test
|
||||
|
||||
.PHONY: test
|
||||
test: $(addprefix test.npm.,$(NPM_TESTS)) ## validate ci suite
|
||||
|
||||
.PHONY: test.npm.*
|
||||
test.npm.%: validate-no-uncommitted-package-lock-changes
|
||||
test -d node_modules || $(MAKE) requirements
|
||||
npm run $(*)
|
||||
|
||||
.PHONY: requirements
|
||||
requirements: ## install ci requirements
|
||||
npm ci
|
||||
|
||||
i18n.extract:
|
||||
# Pulling display strings from .jsx files into .json files...
|
||||
@@ -34,15 +46,15 @@ push_translations:
|
||||
# Pushing strings to Transifex...
|
||||
tx push -s
|
||||
# Fetching hashes from Transifex...
|
||||
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
|
||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
||||
# Writing out comments to file...
|
||||
$(transifex_utils) $(transifex_temp) --comments
|
||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
||||
# Pushing comments to Transifex...
|
||||
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
|
||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
||||
|
||||
# Pulls translations from Transifex.
|
||||
pull_translations:
|
||||
tx pull -f --mode reviewed --language=$(transifex_langs)
|
||||
tx pull -f --mode reviewed --languages=$(transifex_langs)
|
||||
|
||||
# This target is used by Travis.
|
||||
validate-no-uncommitted-package-lock-changes:
|
||||
|
||||
109
README.rst
109
README.rst
@@ -1,53 +1,110 @@
|
||||
|Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||
|
||||
frontend-app-account
|
||||
====================
|
||||
|
||||
This is a micro-frontend application responsible for the display and updating of a user's account information. Please tag **@edx/arch-team** on any PRs or issues.
|
||||
Please tag **@edx/community-engineering** on any PRs or issues. Thanks!
|
||||
|
||||
Development
|
||||
-----------
|
||||
Introduction
|
||||
------------
|
||||
|
||||
Start Devstack
|
||||
^^^^^^^^^^^^^^
|
||||
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
||||
|
||||
To use this application `devstack <https://github.com/edx/devstack>`__ must be running and you must be logged into it.
|
||||
What is the domain of this MFE?
|
||||
|
||||
- Start devstack
|
||||
- Log in (http://localhost:18000/login)
|
||||
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
|
||||
|
||||
Start the development server
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- Account settings page
|
||||
|
||||
In this project, install requirements and start the development server by running:
|
||||
- Demographics collection
|
||||
|
||||
.. code:: bash
|
||||
- IDV (Identity Verification)
|
||||
|
||||
npm install
|
||||
npm start # The server will run on port 1997
|
||||
Installation
|
||||
------------
|
||||
|
||||
Once the dev server is up visit http://localhost:1997.
|
||||
This MFE is bundled with `Devstack <https://github.com/openedx/devstack>`_, see the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ section for setup instructions.
|
||||
|
||||
Configuration and Deployment
|
||||
----------------------------
|
||||
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
|
||||
|
||||
This MFE is configured via node environment variables supplied at build time. See the .env file for the list of required environment variables. Example build syntax with a single environment variable:
|
||||
2. Start up Devstack, if it's not already started.
|
||||
|
||||
3. Log in to Devstack (http://localhost:18000/login )
|
||||
|
||||
4. Within this project, install requirements and start the development server:
|
||||
|
||||
.. code-block::
|
||||
|
||||
npm install
|
||||
npm start # The server will run on port 1997
|
||||
|
||||
5. Once the dev server is up, visit http://localhost:1997 to access the MFE
|
||||
|
||||
.. image:: ./docs/images/localhost_preview.png
|
||||
|
||||
Environment Variables/Setup Notes
|
||||
---------------------------------
|
||||
|
||||
This MFE is configured via environment variables supplied at build time. All micro-frontends have a shared set of required environment variables, as documented in the Open edX Developer Guide under `Required Environment Variables <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||
|
||||
The account settings micro-frontend also supports the following additional variable:
|
||||
|
||||
``SUPPORT_URL``
|
||||
|
||||
Example: ``https://support.example.com``
|
||||
|
||||
The fully-qualified URL to the support page in the target environment.
|
||||
|
||||
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)
|
||||
|
||||
Enables support for a section of the account settings page where a user can enter demographics information. Integrates with a private demographics service and is only used by edx.org.
|
||||
|
||||
``DEMOGRAPHICS_BASE_URL``
|
||||
|
||||
Example: ``https://demographics.example.com``
|
||||
|
||||
Required only if ``ENABLE_DEMOGRAPHICS_COLLECTION`` is true. The fully-qualified URL to the private demographics service in the target environment.
|
||||
|
||||
Example build syntax with a single environment variable:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
||||
|
||||
|
||||
For more information see the document: `Micro-frontend applications in Open
|
||||
edX <https://github.com/edx/edx-developer-docs/blob/5191e800bf16cf42f25c58c58f983bdaf7f9305d/docs/micro-frontends-in-open-edx.rst>`__.
|
||||
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Known Issues
|
||||
------------
|
||||
|
||||
The production Webpack configuration for this repo uses `Purgecss <https://www.purgecss.com/>`__ to remove unused CSS from the production css file. In ``webpack.prod.config.js`` the Purgecss plugin is configured to scan directories to determine what css selectors should remain. Currently the src/ directory is scanned along with all ``@edx/frontend-component*`` node modules and ``@edx/paragon``. **If you add and use a component in this repo that relies on HTML classes or ids for styling you must add it to the Purgecss configuration or it will be unstyled in the production build.**
|
||||
None
|
||||
|
||||
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-account.svg?branch=master
|
||||
:target: https://travis-ci.org/edx/frontend-app-account
|
||||
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.
|
||||
|
||||
|
||||
==============================
|
||||
|
||||
.. |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
|
||||
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-account.svg
|
||||
|
||||
BIN
docs/images/localhost_preview.png
Normal file
BIN
docs/images/localhost_preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -1,7 +1,7 @@
|
||||
const { createConfig } = require('@edx/frontend-build');
|
||||
|
||||
module.exports = createConfig('jest', {
|
||||
setupFiles: [
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/src/setupTest.js',
|
||||
],
|
||||
});
|
||||
|
||||
@@ -3,5 +3,4 @@
|
||||
|
||||
nick: acct
|
||||
oeps: {}
|
||||
owner: edx/arch-team
|
||||
openedx-release: {ref: master}
|
||||
|
||||
47466
package-lock.json
generated
47466
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
package.json
Executable file → Normal file
94
package.json
Executable file → Normal file
@@ -6,44 +6,48 @@
|
||||
"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",
|
||||
"lint": "fedx-scripts eslint",
|
||||
"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/frontend-component-footer": "10.0.9",
|
||||
"@edx/frontend-component-header": "2.0.5",
|
||||
"@edx/frontend-platform": "1.1.14",
|
||||
"@edx/paragon": "7.1.5",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.28",
|
||||
"@fortawesome/free-brands-svg-icons": "5.8.2",
|
||||
"@fortawesome/free-regular-svg-icons": "5.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "5.8.2",
|
||||
"@fortawesome/react-fontawesome": "0.1.9",
|
||||
"babel-polyfill": "6.26.0",
|
||||
"classnames": "2.2.6",
|
||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
||||
"@edx/frontend-component-footer": "11.2.1",
|
||||
"@edx/frontend-component-header": "3.2.1",
|
||||
"@edx/frontend-platform": "2.6.2",
|
||||
"@edx/paragon": "19.20.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.16",
|
||||
"@tensorflow-models/blazeface": "0.0.7",
|
||||
"@tensorflow/tfjs-converter": "3.18.0",
|
||||
"@tensorflow/tfjs-core": "3.18.0",
|
||||
"bowser": "2.11.0",
|
||||
"classnames": "2.3.2",
|
||||
"core-js": "3.19.3",
|
||||
"font-awesome": "4.7.0",
|
||||
"form-urlencoded": "4.0.1",
|
||||
"formdata-polyfill": "3.0.19",
|
||||
"history": "4.10.1",
|
||||
"formdata-polyfill": "4.0.10",
|
||||
"history": "5.3.0",
|
||||
"jslib-html5-camera-photo": "3.1.8",
|
||||
"lodash.camelcase": "4.3.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"lodash.findindex": "4.6.0",
|
||||
@@ -52,37 +56,39 @@
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.omit": "4.5.0",
|
||||
"lodash.pick": "4.4.0",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"memoize-one": "5.1.1",
|
||||
"newrelic": "5.13.1",
|
||||
"lodash.pickby": "4.6.0",
|
||||
"long": "5.2.0",
|
||||
"memoize-one": "5.2.1",
|
||||
"prop-types": "15.7.2",
|
||||
"react": "16.10.2",
|
||||
"react-dom": "16.10.2",
|
||||
"react-redux": "7.1.3",
|
||||
"react-router": "5.1.2",
|
||||
"react-router-dom": "5.1.2",
|
||||
"qs": "6.10.3",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-router": "5.2.1",
|
||||
"react-router-dom": "5.3.0",
|
||||
"react-router-hash-link": "1.2.2",
|
||||
"react-scrollspy": "3.4.2",
|
||||
"react-transition-group": "4.3.0",
|
||||
"redux": "4.0.5",
|
||||
"redux-devtools-extension": "2.13.8",
|
||||
"react-scrollspy": "3.4.3",
|
||||
"react-transition-group": "4.4.5",
|
||||
"redux": "4.1.2",
|
||||
"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.10",
|
||||
"reselect": "4.0.0",
|
||||
"universal-cookie": "4.0.3"
|
||||
"universal-cookie": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "2.0.6",
|
||||
"codecov": "3.6.5",
|
||||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.15.2",
|
||||
"es-check": "5.0.0",
|
||||
"glob": "7.1.6",
|
||||
"husky": "3.0.9",
|
||||
"purgecss-webpack-plugin": "1.6.0",
|
||||
"react-test-renderer": "16.8.6",
|
||||
"@edx/browserslist-config": "1.1.1",
|
||||
"@edx/frontend-build": "12.3.0",
|
||||
"@edx/reactifex": "1.1.0",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/react": "12.1.5",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.6",
|
||||
"react-test-renderer": "16.14.0",
|
||||
"reactifex": "1.1.1",
|
||||
"redux-mock-store": "1.5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<title>Account | edX</title>
|
||||
<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="/favicon.ico" type="image/x-icon" />
|
||||
<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>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -1,9 +1,28 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
"config:base",
|
||||
"schedule:weekly",
|
||||
":automergeLinters",
|
||||
":automergeMinor",
|
||||
":automergeTesters",
|
||||
":enableVulnerabilityAlerts",
|
||||
":rebaseStalePrs",
|
||||
":semanticCommits",
|
||||
":updateNotScheduled"
|
||||
],
|
||||
"patch": {
|
||||
"automerge": true
|
||||
},
|
||||
"rebaseStalePrs": true
|
||||
"packageRules": [
|
||||
{
|
||||
"matchDepTypes": [
|
||||
"devDependencies"
|
||||
],
|
||||
"matchUpdateTypes": [
|
||||
"lockFileMaintenance",
|
||||
"minor",
|
||||
"patch",
|
||||
"pin"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
||||
],
|
||||
"timezone": "America/New_York"
|
||||
}
|
||||
|
||||
@@ -13,29 +13,42 @@ import {
|
||||
getCountryList,
|
||||
getLanguageList,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
import {
|
||||
Button, Hyperlink, Icon, Alert,
|
||||
} from '@edx/paragon';
|
||||
import { CheckCircle, Error, WarningFilled } from '@edx/paragon/icons';
|
||||
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
import { fetchSettings, saveSettings, updateDraft } from './data/actions';
|
||||
import {
|
||||
fetchSettings,
|
||||
saveMultipleSettings,
|
||||
saveSettings,
|
||||
updateDraft,
|
||||
beginNameChange,
|
||||
} from './data/actions';
|
||||
import { accountSettingsPageSelector } from './data/selectors';
|
||||
import PageLoading from './PageLoading';
|
||||
import Alert from './Alert';
|
||||
import JumpNav from './JumpNav';
|
||||
import DeleteAccount from './delete-account';
|
||||
import EditableField from './EditableField';
|
||||
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,
|
||||
getStatesList,
|
||||
} from './data/constants';
|
||||
import { fetchSiteLanguages } from './site-language';
|
||||
import CoachingToggle from './coaching/CoachingToggle';
|
||||
import DemographicsSection from './demographics/DemographicsSection';
|
||||
|
||||
class AccountSettingsPage extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -43,8 +56,8 @@ class AccountSettingsPage extends React.Component {
|
||||
|
||||
// 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 Open edX account. We use this to display a message to that effect, and remove the
|
||||
// parameter from the URL.
|
||||
// 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);
|
||||
@@ -52,6 +65,16 @@ class AccountSettingsPage extends React.Component {
|
||||
this.state = {
|
||||
duplicateTpaProvider,
|
||||
};
|
||||
|
||||
this.navLinkRefs = {
|
||||
'#basic-information': React.createRef(),
|
||||
'#profile-information': React.createRef(),
|
||||
'#demographics-information': React.createRef(),
|
||||
'#social-media': React.createRef(),
|
||||
'#site-preferences': React.createRef(),
|
||||
'#linked-accounts': React.createRef(),
|
||||
'#delete-account': React.createRef(),
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -64,6 +87,20 @@ class AccountSettingsPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.loading && !prevProps.loaded && this.props.loaded) {
|
||||
const locationHash = global.location.hash;
|
||||
// Check for the locationHash in the URL and then scroll to it if it is in the
|
||||
// NavLinks list
|
||||
if (typeof locationHash !== 'string') {
|
||||
return;
|
||||
}
|
||||
if (Object.keys(this.navLinkRefs).includes(locationHash) && this.navLinkRefs[locationHash].current) {
|
||||
window.scrollTo(0, this.navLinkRefs[locationHash].current.offsetTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: We need 'locale' for the memoization in getLocalizedTimeZoneOptions. Don't remove it!
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getLocalizedTimeZoneOptions = memoize((timeZoneOptions, countryTimeZoneOptions, locale) => {
|
||||
@@ -111,6 +148,42 @@ class AccountSettingsPage extends React.Component {
|
||||
})),
|
||||
}));
|
||||
|
||||
handleEditableFieldChange = (name, value) => {
|
||||
this.props.updateDraft(name, value);
|
||||
};
|
||||
|
||||
handleSubmit = (formId, values) => {
|
||||
this.props.saveSettings(formId, values);
|
||||
};
|
||||
|
||||
handleSubmitProfileName = (formId, values) => {
|
||||
if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) {
|
||||
this.props.saveMultipleSettings([
|
||||
{
|
||||
formId,
|
||||
commitValues: values,
|
||||
},
|
||||
{
|
||||
formId: 'useVerifiedNameForCerts',
|
||||
commitValues: this.props.formValues.useVerifiedNameForCerts,
|
||||
},
|
||||
], formId);
|
||||
} else {
|
||||
this.props.saveSettings(formId, values);
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmitVerifiedName = (formId, values) => {
|
||||
if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) {
|
||||
this.props.saveSettings('useVerifiedNameForCerts', this.props.formValues.useVerifiedNameForCerts);
|
||||
}
|
||||
if (values !== this.props.committedValues?.verified_name) {
|
||||
this.props.beginNameChange(formId);
|
||||
} else {
|
||||
this.props.saveSettings(formId, values);
|
||||
}
|
||||
};
|
||||
|
||||
isEditable(fieldName) {
|
||||
return !this.props.staticFields.includes(fieldName);
|
||||
}
|
||||
@@ -121,14 +194,6 @@ class AccountSettingsPage extends React.Component {
|
||||
return Boolean(this.props.profileDataManager);
|
||||
}
|
||||
|
||||
handleEditableFieldChange = (name, value) => {
|
||||
this.props.updateDraft(name, value);
|
||||
};
|
||||
|
||||
handleSubmit = (formId, values) => {
|
||||
this.props.saveSettings(formId, values);
|
||||
};
|
||||
|
||||
renderDuplicateTpaProviderMessage() {
|
||||
if (!this.state.duplicateTpaProvider) {
|
||||
return null;
|
||||
@@ -136,13 +201,14 @@ class AccountSettingsPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Alert className="alert alert-danger" role="alert">
|
||||
<Alert variant="danger">
|
||||
<FormattedMessage
|
||||
id="account.settings.message.duplicate.tpa.provider"
|
||||
defaultMessage="The {provider} account you selected is already linked to another edX account."
|
||||
description="alert message informing the user that the third-party account they attempted to link is already linked to another edX account"
|
||||
defaultMessage="The {provider} account you selected is already linked to another {siteName} account."
|
||||
description="alert message informing the user that the third-party account they attempted to link is already linked to another account"
|
||||
values={{
|
||||
provider: <b>{this.state.duplicateTpaProvider}</b>,
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
@@ -157,7 +223,7 @@ class AccountSettingsPage extends React.Component {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Alert className="alert alert-primary" role="alert">
|
||||
<Alert variant="info">
|
||||
<FormattedMessage
|
||||
id="account.settings.message.managed.settings"
|
||||
defaultMessage="Your profile settings are managed by {managerTitle}. Contact your administrator or {support} for help."
|
||||
@@ -180,6 +246,170 @@ class AccountSettingsPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderFullNameHelpText = (status, proctoredExamId) => {
|
||||
if (!this.props.verifiedNameHistory) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text']);
|
||||
}
|
||||
|
||||
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();
|
||||
const id = `dismissedVerifiedNameSuccessMessage-${verifiedName}-${dateValue}`;
|
||||
|
||||
return (
|
||||
<OneTimeDismissibleAlert
|
||||
id={id}
|
||||
variant="success"
|
||||
icon={CheckCircle}
|
||||
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.success.message.header'])}
|
||||
body={this.props.intl.formatMessage(messages['account.settings.field.name.verified.success.message'])}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderVerifiedNameFailureMessage = (verifiedName, created) => {
|
||||
const dateValue = new Date(created).valueOf();
|
||||
const id = `dismissedVerifiedNameFailureMessage-${verifiedName}-${dateValue}`;
|
||||
|
||||
return (
|
||||
<OneTimeDismissibleAlert
|
||||
id={id}
|
||||
variant="danger"
|
||||
icon={Error}
|
||||
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.header'])}
|
||||
body={
|
||||
(
|
||||
<>
|
||||
<div className="d-flex flex-row">
|
||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message'])}
|
||||
</div>
|
||||
<div className="d-flex flex-row-reverse mt-3">
|
||||
<Button
|
||||
variant="primary"
|
||||
href="https://support.edx.org/hc/en-us/articles/360004381594-Why-was-my-ID-verification-denied"
|
||||
>
|
||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.help.link'])}
|
||||
</Button>{' '}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderVerifiedNameSubmittedMessage = (willCertNameChange) => (
|
||||
<Alert
|
||||
variant="warning"
|
||||
icon={WarningFilled}
|
||||
>
|
||||
<Alert.Heading>
|
||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.submitted.message.header'])}
|
||||
</Alert.Heading>
|
||||
<p>
|
||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.submitted.message'])}{' '}
|
||||
{
|
||||
willCertNameChange
|
||||
&& this.props.intl.formatMessage(messages['account.settings.field.name.verified.submitted.message.certificate'])
|
||||
}
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
renderVerifiedNameMessage = verifiedNameRecord => {
|
||||
const {
|
||||
created,
|
||||
status,
|
||||
profile_name: profileName,
|
||||
verified_name: verifiedName,
|
||||
proctored_exam_attempt_id: proctoredExamId,
|
||||
} = verifiedNameRecord;
|
||||
let willCertNameChange = false;
|
||||
|
||||
if (
|
||||
(
|
||||
// User submitted a profile name change, and uses their profile name on certificates
|
||||
this.props.committedValues.name !== profileName
|
||||
&& !this.props.committedValues.useVerifiedNameForCerts
|
||||
)
|
||||
|| (
|
||||
// User submitted a verified name change, and uses their verified name on certificates
|
||||
this.props.committedValues.name === profileName
|
||||
&& this.props.committedValues.useVerifiedNameForCerts
|
||||
)
|
||||
) {
|
||||
willCertNameChange = true;
|
||||
}
|
||||
|
||||
if (proctoredExamId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return this.renderVerifiedNameSuccessMessage(verifiedName, created);
|
||||
case 'denied':
|
||||
return this.renderVerifiedNameFailureMessage(verifiedName, created);
|
||||
case 'submitted':
|
||||
return this.renderVerifiedNameSubmittedMessage(willCertNameChange);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
renderVerifiedNameIcon = (status) => {
|
||||
switch (status) {
|
||||
case 'approved':
|
||||
return (<Icon src={CheckCircle} className="ml-1" style={{ height: '18px', width: '18px', color: 'green' }} />);
|
||||
case 'submitted':
|
||||
return (<Icon src={WarningFilled} className="ml-1" style={{ height: '18px', width: '18px', color: 'yellow' }} />);
|
||||
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()) {
|
||||
return this.props.intl.formatMessage(messages['account.settings.static.field.empty'], {
|
||||
@@ -189,8 +419,15 @@ class AccountSettingsPage extends React.Component {
|
||||
return this.props.intl.formatMessage(messages['account.settings.static.field.empty.no.admin']);
|
||||
}
|
||||
|
||||
renderNameChangeModal() {
|
||||
if (this.props.nameChangeModal && this.props.nameChangeModal.formId) {
|
||||
return <NameChange targetFormId={this.props.nameChangeModal.formId} />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderSecondaryEmailField(editableFieldProps) {
|
||||
if (!Boolean(this.props.formValues.secondary_email_enabled)) {
|
||||
if (!this.props.formValues.secondary_email_enabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -206,6 +443,16 @@ class AccountSettingsPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderDemographicsSection() {
|
||||
// check the result of an LMS API call to determine if we should render the DemographicsSection component
|
||||
if (this.props.formValues.shouldDisplayDemographicsSection) {
|
||||
return (
|
||||
<DemographicsSection forwardRef={this.navLinkRefs['#demographics-information']} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
const editableFieldProps = {
|
||||
onChange: this.handleEditableFieldChange,
|
||||
@@ -223,7 +470,9 @@ class AccountSettingsPage extends React.Component {
|
||||
} = 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 showState = this.props.formValues.country === COUNTRY_WITH_STATES;
|
||||
|
||||
const { verifiedName } = this.props;
|
||||
|
||||
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
||||
this.props.timeZoneOptions,
|
||||
@@ -233,63 +482,143 @@ 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 (
|
||||
<React.Fragment>
|
||||
<div className="account-section" id="basic-information">
|
||||
<h2 className="section-heading">
|
||||
<>
|
||||
{ shouldUpdateDOB
|
||||
&& (
|
||||
<DOBModal
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
)}
|
||||
<div className="account-section pt-3 mb-5" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
|
||||
{
|
||||
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 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>
|
||||
{this.renderManagedProfileMessage()}
|
||||
|
||||
{this.renderNameChangeModal()}
|
||||
|
||||
<EditableField
|
||||
name="username"
|
||||
type="text"
|
||||
value={this.props.formValues.username}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.username'])}
|
||||
helpText={this.props.intl.formatMessage(messages['account.settings.field.username.help.text'])}
|
||||
helpText={this.props.intl.formatMessage(
|
||||
messages['account.settings.field.username.help.text'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
isEditable={false}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="name"
|
||||
type="text"
|
||||
value={this.props.formValues.name}
|
||||
value={
|
||||
verifiedName?.status === 'submitted'
|
||||
&& this.props.formValues.pending_name_change
|
||||
? this.props.formValues.pending_name_change
|
||||
: this.props.formValues.name
|
||||
}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.full.name'])}
|
||||
emptyLabel={
|
||||
this.isEditable('name') ?
|
||||
this.props.intl.formatMessage(messages['account.settings.field.full.name.empty']) :
|
||||
this.renderEmptyStaticFieldMessage()
|
||||
this.isEditable('name')
|
||||
? this.props.intl.formatMessage(messages['account.settings.field.full.name.empty'])
|
||||
: this.renderEmptyStaticFieldMessage()
|
||||
}
|
||||
helpText={this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text'])}
|
||||
isEditable={this.isEditable('name')}
|
||||
{...editableFieldProps}
|
||||
helpText={
|
||||
verifiedName
|
||||
? this.renderFullNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)
|
||||
: this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text'])
|
||||
}
|
||||
isEditable={
|
||||
verifiedName
|
||||
? this.isEditable('verifiedName') && this.isEditable('name')
|
||||
: this.isEditable('name')
|
||||
}
|
||||
isGrayedOut={
|
||||
verifiedName && !this.isEditable('verifiedName')
|
||||
}
|
||||
onChange={this.handleEditableFieldChange}
|
||||
onSubmit={this.handleSubmitProfileName}
|
||||
/>
|
||||
{verifiedName
|
||||
&& (
|
||||
<EditableField
|
||||
name="verified_name"
|
||||
type="text"
|
||||
value={this.props.formValues.verified_name}
|
||||
label={
|
||||
(
|
||||
<div className="d-flex">
|
||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified'])}
|
||||
{
|
||||
this.renderVerifiedNameIcon(verifiedName.status)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
helpText={this.renderVerifiedNameHelpText(verifiedName.status, verifiedName.proctored_exam_attempt_id)}
|
||||
isEditable={this.isEditable('verifiedName')}
|
||||
isGrayedOut={!this.isEditable('verifiedName')}
|
||||
onChange={this.handleEditableFieldChange}
|
||||
onSubmit={this.handleSubmitVerifiedName}
|
||||
/>
|
||||
)}
|
||||
|
||||
<EmailField
|
||||
name="email"
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.email'])}
|
||||
emptyLabel={
|
||||
this.isEditable('email') ?
|
||||
this.props.intl.formatMessage(messages['account.settings.field.email.empty']) :
|
||||
this.renderEmptyStaticFieldMessage()
|
||||
this.isEditable('email')
|
||||
? this.props.intl.formatMessage(messages['account.settings.field.email.empty'])
|
||||
: this.renderEmptyStaticFieldMessage()
|
||||
}
|
||||
value={this.props.formValues.email}
|
||||
confirmationMessageDefinition={messages['account.settings.field.email.confirmation']}
|
||||
helpText={this.props.intl.formatMessage(messages['account.settings.field.email.help.text'])}
|
||||
helpText={this.props.intl.formatMessage(
|
||||
messages['account.settings.field.email.help.text'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
isEditable={this.isEditable('email')}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
{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}
|
||||
/>
|
||||
{(!getConfig().ENABLE_COPPA_COMPLIANCE)
|
||||
&& (
|
||||
<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
|
||||
name="country"
|
||||
type="select"
|
||||
@@ -297,14 +626,15 @@ class AccountSettingsPage extends React.Component {
|
||||
options={countryOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.country'])}
|
||||
emptyLabel={
|
||||
this.isEditable('country') ?
|
||||
this.props.intl.formatMessage(messages['account.settings.field.country.empty']) :
|
||||
this.renderEmptyStaticFieldMessage()
|
||||
this.isEditable('country')
|
||||
? this.props.intl.formatMessage(messages['account.settings.field.country.empty'])
|
||||
: this.renderEmptyStaticFieldMessage()
|
||||
}
|
||||
isEditable={this.isEditable('country')}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
{showState &&
|
||||
{showState
|
||||
&& (
|
||||
<EditableField
|
||||
name="state"
|
||||
type="select"
|
||||
@@ -312,18 +642,18 @@ class AccountSettingsPage extends React.Component {
|
||||
options={stateOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.state'])}
|
||||
emptyLabel={
|
||||
this.isEditable('state') ?
|
||||
this.props.intl.formatMessage(messages['account.settings.field.state.empty']) :
|
||||
this.renderEmptyStaticFieldMessage()
|
||||
this.isEditable('state')
|
||||
? this.props.intl.formatMessage(messages['account.settings.field.state.empty'])
|
||||
: this.renderEmptyStaticFieldMessage()
|
||||
}
|
||||
isEditable={this.isEditable('state')}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="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>
|
||||
|
||||
@@ -354,21 +684,27 @@ 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 &&
|
||||
{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>
|
||||
|
||||
<div className="account-section" id="social-media">
|
||||
<h2 className="section-heading">
|
||||
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
|
||||
<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>{this.props.intl.formatMessage(messages['account.settings.section.social.media.description'])}</p>
|
||||
<p>
|
||||
{this.props.intl.formatMessage(
|
||||
messages['account.settings.section.social.media.description'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
|
||||
<EditableField
|
||||
name="social_link_linkedin"
|
||||
@@ -396,8 +732,8 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="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>
|
||||
|
||||
@@ -427,20 +763,25 @@ class AccountSettingsPage extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="linked-accounts">
|
||||
<h2 className="section-heading">{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts'])}</h2>
|
||||
<p>{this.props.intl.formatMessage(messages['account.settings.section.linked.accounts.description'])}</p>
|
||||
<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'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<ThirdPartyAuth />
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="delete-account">
|
||||
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
||||
<DeleteAccount
|
||||
isVerifiedAccount={this.props.isActive}
|
||||
hasLinkedTPA={hasLinkedTPA}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -476,7 +817,9 @@ class AccountSettingsPage extends React.Component {
|
||||
<div>
|
||||
<div className="row">
|
||||
<div className="col-md-3">
|
||||
<JumpNav />
|
||||
<JumpNav
|
||||
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-9">
|
||||
{loading ? this.renderLoading() : null}
|
||||
@@ -504,11 +847,13 @@ AccountSettingsPage.propTypes = {
|
||||
name: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
secondary_email: PropTypes.string,
|
||||
secondary_email_enabled: PropTypes.bool,
|
||||
year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
country: PropTypes.string,
|
||||
level_of_education: PropTypes.string,
|
||||
gender: PropTypes.string,
|
||||
language_proficiencies: PropTypes.string,
|
||||
pending_name_change: PropTypes.string,
|
||||
phone_number: PropTypes.string,
|
||||
social_link_linkedin: PropTypes.string,
|
||||
social_link_facebook: PropTypes.string,
|
||||
@@ -519,7 +864,20 @@ AccountSettingsPage.propTypes = {
|
||||
user: PropTypes.number.isRequired,
|
||||
eligible_for_coaching: PropTypes.bool.isRequired,
|
||||
}),
|
||||
state: PropTypes.string,
|
||||
shouldDisplayDemographicsSection: PropTypes.bool,
|
||||
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
||||
verified_name: PropTypes.string,
|
||||
}).isRequired,
|
||||
committedValues: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
useVerifiedNameForCerts: PropTypes.bool,
|
||||
verified_name: PropTypes.string,
|
||||
}),
|
||||
drafts: PropTypes.shape({}),
|
||||
formErrors: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
}),
|
||||
siteLanguage: PropTypes.shape({
|
||||
previousValue: PropTypes.string,
|
||||
draft: PropTypes.string,
|
||||
@@ -543,15 +901,45 @@ AccountSettingsPage.propTypes = {
|
||||
})),
|
||||
fetchSiteLanguages: PropTypes.func.isRequired,
|
||||
updateDraft: PropTypes.func.isRequired,
|
||||
saveMultipleSettings: PropTypes.func.isRequired,
|
||||
saveSettings: PropTypes.func.isRequired,
|
||||
fetchSettings: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.arrayOf(PropTypes.object),
|
||||
beginNameChange: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||
connected: PropTypes.bool,
|
||||
})),
|
||||
nameChangeModal: PropTypes.shape({
|
||||
formId: PropTypes.string,
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
AccountSettingsPage.defaultProps = {
|
||||
loading: false,
|
||||
loaded: false,
|
||||
loadingError: null,
|
||||
committedValues: {
|
||||
useVerifiedNameForCerts: false,
|
||||
verified_name: null,
|
||||
},
|
||||
drafts: {},
|
||||
formErrors: {},
|
||||
siteLanguage: null,
|
||||
siteLanguageOptions: [],
|
||||
timeZoneOptions: [],
|
||||
@@ -561,11 +949,17 @@ AccountSettingsPage.defaultProps = {
|
||||
tpaProviders: [],
|
||||
isActive: true,
|
||||
secondary_email_enabled: false,
|
||||
nameChangeModal: {},
|
||||
verifiedName: null,
|
||||
mostRecentVerifiedName: {},
|
||||
verifiedNameHistory: [],
|
||||
};
|
||||
|
||||
export default connect(accountSettingsPageSelector, {
|
||||
fetchSettings,
|
||||
saveSettings,
|
||||
saveMultipleSettings,
|
||||
updateDraft,
|
||||
fetchSiteLanguages,
|
||||
beginNameChange,
|
||||
})(injectIntl(AccountSettingsPage));
|
||||
|
||||
@@ -46,6 +46,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Profile Information',
|
||||
description: 'The profile information section heading.',
|
||||
},
|
||||
'account.settings.section.demographics.information': {
|
||||
id: 'account.settings.section.demographics.information',
|
||||
defaultMessage: 'Optional Information',
|
||||
description: 'The optional information section heading.',
|
||||
},
|
||||
'account.settings.section.site.preferences': {
|
||||
id: 'account.settings.section.site.preferences',
|
||||
defaultMessage: 'Site Preferences',
|
||||
@@ -58,7 +63,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.section.linked.accounts.description': {
|
||||
id: 'account.settings.section.linked.accounts.description',
|
||||
defaultMessage: 'You can link your identity accounts to simplify signing in to edX.',
|
||||
defaultMessage: 'You can link your identity accounts to simplify signing in to {siteName}.',
|
||||
description: 'The linked accounts section heading description.',
|
||||
},
|
||||
'account.settings.field.username': {
|
||||
@@ -68,7 +73,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.field.username.help.text': {
|
||||
id: 'account.settings.field.username.help.text',
|
||||
defaultMessage: 'The name that identifies you on edX. You cannot change your username.',
|
||||
defaultMessage: 'The name that identifies you on {siteName}. You cannot change your username.',
|
||||
description: 'Help text for the account settings username field.',
|
||||
},
|
||||
'account.settings.field.full.name': {
|
||||
@@ -86,6 +91,126 @@ 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.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.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.',
|
||||
},
|
||||
'account.settings.field.name.verified': {
|
||||
id: 'account.settings.field.name.verified',
|
||||
defaultMessage: 'Verified name',
|
||||
description: 'Label for account settings verified name field.',
|
||||
},
|
||||
'account.settings.field.name.verified.help.text.verified': {
|
||||
id: 'account.settings.field.name.verified.help.text.verified',
|
||||
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.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 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': {
|
||||
id: 'account.settings.field.full.name.help.text.submitted',
|
||||
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
||||
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.',
|
||||
description: 'The body of the success alert indicating that a user\'s name has been verified',
|
||||
},
|
||||
'account.settings.field.name.verified.success.message.header': {
|
||||
id: 'account.settings.field.name.verified.success.message.header',
|
||||
defaultMessage: 'Your name change request is complete!',
|
||||
description: 'The header of the success alert indicating that a user\'s name has been verified',
|
||||
},
|
||||
'account.settings.field.name.verified.failure.message': {
|
||||
id: 'account.settings.field.name.verified.failure.message',
|
||||
defaultMessage: 'Your most recent identity verification attempt did not pass. Related account settings have been restored.',
|
||||
description: 'The body of the failure alert indicating that a user\'s name was not able to be verified',
|
||||
},
|
||||
'account.settings.field.name.verified.failure.message.header': {
|
||||
id: 'account.settings.field.name.verified.failure.message.header',
|
||||
defaultMessage: 'We were not able to verify your identity.',
|
||||
description: 'The header of the failure alert indicating that a user\'s name was not able to be verified',
|
||||
},
|
||||
'account.settings.field.name.verified.failure.message.help.link': {
|
||||
id: 'account.settings.field.name.verified.failure.message.help.link',
|
||||
defaultMessage: 'Learn more about ID verification',
|
||||
description: 'The text of the button displayed when a user\'s name was not able to be verified, intended to direct the user to a help article about ID verification.',
|
||||
},
|
||||
'account.settings.field.name.verified.submitted.message': {
|
||||
id: 'account.settings.field.name.verified.submitted.message',
|
||||
defaultMessage: 'Your identity verification request has been submitted and usually takes between 24 and 48 hours to complete.',
|
||||
description: 'The body of the submitted alert indicating that a user\'s name has been submitted for verification',
|
||||
},
|
||||
'account.settings.field.name.verified.submitted.message.certificate': {
|
||||
id: 'account.settings.field.name.verified.submitted.message.certificate',
|
||||
defaultMessage: 'When your request is approved, your updated name will appear on all associated certificates and public-facing records.',
|
||||
description: 'The body of the submitted alert indicating that a user\'s name will be updated on certificates.',
|
||||
},
|
||||
'account.settings.field.name.verified.submitted.message.header': {
|
||||
id: 'account.settings.field.name.verified.submitted.message.header',
|
||||
defaultMessage: 'Your name change request is almost complete!',
|
||||
description: 'The header of the submitted alert indicating that a user\'s name has been submitted for verification',
|
||||
},
|
||||
'account.settings.field.email': {
|
||||
id: 'account.settings.field.email',
|
||||
defaultMessage: 'Email address (Sign in)',
|
||||
@@ -103,7 +228,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.field.email.help.text': {
|
||||
id: 'account.settings.field.email.help.text',
|
||||
defaultMessage: 'You receive messages from edX and course teams at this address.',
|
||||
defaultMessage: 'You receive messages from {siteName} and course teams at this address.',
|
||||
description: 'Help text for the account settings email field.',
|
||||
},
|
||||
'account.settings.field.secondary.email': {
|
||||
@@ -141,6 +266,46 @@ 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.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: 'Confirm your date of birth',
|
||||
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 date 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 birth date 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',
|
||||
@@ -274,19 +439,20 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.field.language.proficiencies': {
|
||||
id: 'account.settings.field.language.proficiencies',
|
||||
defaultMessage: 'Spoken languages',
|
||||
description: 'Label for account settings spoken languages field.',
|
||||
defaultMessage: 'Spoken language',
|
||||
description: 'Label for account settings spoken language field.',
|
||||
},
|
||||
'account.settings.field.language.proficiencies.empty': {
|
||||
id: 'account.settings.field.language.proficiencies.empty',
|
||||
defaultMessage: 'Add a spoken language',
|
||||
description: 'Placeholder for empty account settings spoken languages field.',
|
||||
description: 'Placeholder for empty account settings spoken language field.',
|
||||
},
|
||||
'account.settings.field.language_proficiencies.options.empty': {
|
||||
id: 'account.settings.field.language_proficiencies.options.empty',
|
||||
defaultMessage: 'Select a Language',
|
||||
description: 'Option for an empty value on account settings spoken languages field.',
|
||||
description: 'Option for an empty value on account settings spoken language field.',
|
||||
},
|
||||
|
||||
'account.settings.field.time.zone': {
|
||||
id: 'account.settings.field.time.zone',
|
||||
defaultMessage: 'Time zone',
|
||||
@@ -325,7 +491,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.section.social.media.description': {
|
||||
id: 'account.settings.section.social.media.description',
|
||||
defaultMessage: 'Optionally, link your personal accounts to the social media icons on your edX profile.',
|
||||
defaultMessage: 'Optionally, link your personal accounts to the social media icons on your {siteName} profile.',
|
||||
description: 'Section subheader for social media links settings',
|
||||
},
|
||||
'account.settings.field.social.platform.name.linkedin': {
|
||||
|
||||
@@ -2,20 +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,
|
||||
@@ -29,5 +25,4 @@ Alert.defaultProps = {
|
||||
children: undefined,
|
||||
};
|
||||
|
||||
|
||||
export default Alert;
|
||||
|
||||
@@ -65,7 +65,7 @@ class BetaLanguageBanner extends React.Component {
|
||||
})}
|
||||
</p>
|
||||
<div>
|
||||
<Button onClick={this.handleRevertLanguage} className="btn btn-primary mr-2">
|
||||
<Button onClick={this.handleRevertLanguage} className="mr-2">
|
||||
{this.props.intl.formatMessage(
|
||||
messages['account.settings.banner.beta.language.action.switch.back'],
|
||||
{ previous_language: previousLanguage.name },
|
||||
|
||||
148
src/account-settings/DOBForm.jsx
Normal file
148
src/account-settings/DOBForm.jsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
||||
} from '@edx/paragon';
|
||||
import React, { useCallback, useEffect } 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 handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const month = e.target.month.value;
|
||||
const year = e.target.year.value;
|
||||
const data = month !== '' && year !== '' ? [{ field_name: 'DOB', field_value: `${year}-${month}` }] : [];
|
||||
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]);
|
||||
|
||||
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"
|
||||
>
|
||||
{[...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"
|
||||
>
|
||||
{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={saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
</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,8 +1,12 @@
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
import React from 'react';
|
||||
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, Input, StatefulButton, ValidationFormGroup } from '@edx/paragon';
|
||||
import {
|
||||
Button, Input, StatefulButton, ValidationFormGroup,
|
||||
} from '@edx/paragon';
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
@@ -14,15 +18,16 @@ import {
|
||||
closeForm,
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
import CertificatePreference from './certificate-preference/CertificatePreference';
|
||||
|
||||
|
||||
function EditableField(props) {
|
||||
const EditableField = (props) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
emptyLabel,
|
||||
type,
|
||||
value,
|
||||
userSuppliedValue,
|
||||
options,
|
||||
saveState,
|
||||
error,
|
||||
@@ -35,10 +40,15 @@ function EditableField(props) {
|
||||
onChange,
|
||||
isEditing,
|
||||
isEditable,
|
||||
isGrayedOut,
|
||||
intl,
|
||||
...others
|
||||
} = props;
|
||||
const id = `field-${name}`;
|
||||
let inputOptions = options;
|
||||
if (getConfig().ENABLE_COPPA_COMPLIANCE && name === 'level_of_education' && options) {
|
||||
inputOptions = options.filter(option => option.value !== 'el');
|
||||
}
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
@@ -59,26 +69,37 @@ function EditableField(props) {
|
||||
|
||||
const renderEmptyLabel = () => {
|
||||
if (isEditable) {
|
||||
return <Button onClick={handleEdit} className="btn-link p-0">{emptyLabel}</Button>;
|
||||
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();
|
||||
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) return selectedOption.label;
|
||||
if (selectedOption) {
|
||||
finalValue = selectedOption.label;
|
||||
}
|
||||
}
|
||||
|
||||
return rawValue;
|
||||
if (userSuppliedValue) {
|
||||
finalValue += `: ${userSuppliedValue}`;
|
||||
}
|
||||
|
||||
return finalValue;
|
||||
};
|
||||
|
||||
const renderConfirmationMessage = () => {
|
||||
if (!confirmationMessageDefinition || !confirmationValue) return null;
|
||||
if (!confirmationMessageDefinition || !confirmationValue) {
|
||||
return null;
|
||||
}
|
||||
return intl.formatMessage(confirmationMessageDefinition, {
|
||||
value: confirmationValue,
|
||||
});
|
||||
@@ -89,79 +110,84 @@ function EditableField(props) {
|
||||
expression={isEditing ? 'editing' : 'default'}
|
||||
cases={{
|
||||
editing: (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={id}
|
||||
invalid={error != null}
|
||||
invalidMessage={error}
|
||||
helpText={helpText}
|
||||
>
|
||||
<label className="h6 d-block" htmlFor={id}>{label}</label>
|
||||
<Input
|
||||
name={name}
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
{...others}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
<p>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="btn-primary 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
|
||||
onClick={handleCancel}
|
||||
className="btn-outline-primary"
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<ValidationFormGroup
|
||||
for={id}
|
||||
invalid={error != null}
|
||||
invalidMessage={error}
|
||||
helpText={helpText}
|
||||
>
|
||||
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
||||
</Button>
|
||||
</p>
|
||||
</form>
|
||||
<label className="h6 d-block" htmlFor={id}>{label}</label>
|
||||
<Input
|
||||
data-hj-suppress
|
||||
name={name}
|
||||
id={id}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
options={inputOptions}
|
||||
{...others}
|
||||
/>
|
||||
<>{others.children}</>
|
||||
</ValidationFormGroup>
|
||||
<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 onClick={handleEdit} className="ml-3 btn-link">
|
||||
<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>{renderValue(value)}</p>
|
||||
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
|
||||
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
EditableField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
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]),
|
||||
@@ -181,6 +207,7 @@ EditableField.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
isEditing: PropTypes.bool,
|
||||
isEditable: PropTypes.bool,
|
||||
isGrayedOut: PropTypes.bool,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
@@ -196,9 +223,10 @@ EditableField.defaultProps = {
|
||||
helpText: undefined,
|
||||
isEditing: false,
|
||||
isEditable: true,
|
||||
isGrayedOut: false,
|
||||
userSuppliedValue: undefined,
|
||||
};
|
||||
|
||||
|
||||
export default connect(editableFieldSelector, {
|
||||
onEdit: openForm,
|
||||
onCancel: closeForm,
|
||||
|
||||
@@ -2,7 +2,9 @@ import React from 'react';
|
||||
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';
|
||||
import {
|
||||
Button, StatefulButton, Input, ValidationFormGroup,
|
||||
} from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
@@ -16,8 +18,7 @@ import {
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
|
||||
|
||||
function EmailField(props) {
|
||||
const EmailField = (props) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
@@ -56,7 +57,9 @@ function EmailField(props) {
|
||||
};
|
||||
|
||||
const renderConfirmationMessage = () => {
|
||||
if (!confirmationMessageDefinition || !confirmationValue) return null;
|
||||
if (!confirmationMessageDefinition || !confirmationValue) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Alert
|
||||
className="alert-warning mt-n2"
|
||||
@@ -85,13 +88,15 @@ function EmailField(props) {
|
||||
|
||||
const renderEmptyLabel = () => {
|
||||
if (isEditable) {
|
||||
return <Button onClick={handleEdit} className="btn-link p-0">{emptyLabel}</Button>;
|
||||
return <Button variant="link" onClick={handleEdit} className="p-0">{emptyLabel}</Button>;
|
||||
}
|
||||
return <span className="text-muted">{emptyLabel}</span>;
|
||||
};
|
||||
|
||||
const renderValue = () => {
|
||||
if (confirmationValue) return renderConfirmationValue();
|
||||
if (confirmationValue) {
|
||||
return renderConfirmationValue();
|
||||
}
|
||||
return value || renderEmptyLabel();
|
||||
};
|
||||
|
||||
@@ -109,6 +114,7 @@ function EmailField(props) {
|
||||
>
|
||||
<label className="h6 d-block" htmlFor={id}>{label}</label>
|
||||
<Input
|
||||
data-hj-suppress
|
||||
name={name}
|
||||
id={id}
|
||||
type="email"
|
||||
@@ -119,7 +125,7 @@ function EmailField(props) {
|
||||
<p>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
className="btn-primary mr-2"
|
||||
className="mr-2"
|
||||
state={saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||
@@ -132,13 +138,13 @@ function EmailField(props) {
|
||||
// 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();
|
||||
if (saveState === 'pending') { e.preventDefault(); }
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={handleCancel}
|
||||
className="btn-outline-primary"
|
||||
>
|
||||
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
||||
</Button>
|
||||
@@ -150,21 +156,20 @@ function EmailField(props) {
|
||||
<div className="d-flex align-items-start">
|
||||
<h6 aria-level="3">{label}</h6>
|
||||
{isEditable ? (
|
||||
<Button onClick={handleEdit} className="ml-3 btn-link">
|
||||
<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>{renderValue()}</p>
|
||||
<p data-hj-suppress>{renderValue()}</p>
|
||||
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
EmailField.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
@@ -202,7 +207,6 @@ EmailField.defaultProps = {
|
||||
isEditable: true,
|
||||
};
|
||||
|
||||
|
||||
export default connect(editableFieldSelector, {
|
||||
onEdit: openForm,
|
||||
onCancel: closeForm,
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import React from 'react';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { NavHashLink } from 'react-router-hash-link';
|
||||
import Scrollspy from 'react-scrollspy';
|
||||
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
|
||||
|
||||
function JumpNav({ intl }) {
|
||||
const JumpNav = ({
|
||||
intl,
|
||||
displayDemographicsLink,
|
||||
}) => {
|
||||
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
||||
return (
|
||||
<div className="jump-nav">
|
||||
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
||||
<Scrollspy
|
||||
items={[
|
||||
'basic-information',
|
||||
'profile-information',
|
||||
'demographics-information',
|
||||
'social-media',
|
||||
'site-preferences',
|
||||
'linked-accounts',
|
||||
@@ -31,6 +38,14 @@ function JumpNav({ intl }) {
|
||||
{intl.formatMessage(messages['account.settings.section.profile.information'])}
|
||||
</NavHashLink>
|
||||
</li>
|
||||
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && displayDemographicsLink
|
||||
&& (
|
||||
<li>
|
||||
<NavHashLink to="#demographics-information">
|
||||
{intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
||||
</NavHashLink>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<NavHashLink to="#social-media">
|
||||
{intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||
@@ -54,12 +69,11 @@ function JumpNav({ intl }) {
|
||||
</Scrollspy>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
JumpNav.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
displayDemographicsLink: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default injectIntl(JumpNav);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||
<FormattedMessage
|
||||
id="error.notfound.message"
|
||||
defaultMessage="The page you're looking for is unavailable or there's an error in the URL. Please check the URL and try again."
|
||||
description="error message when a page does not exist"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const NotFoundPage = () => (
|
||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
||||
<p className="my-0 py-5 text-muted" 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;
|
||||
|
||||
45
src/account-settings/OneTimeDismissibleAlert.jsx
Normal file
45
src/account-settings/OneTimeDismissibleAlert.jsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
const OneTimeDismissibleAlert = (props) => {
|
||||
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
||||
|
||||
const onClose = () => {
|
||||
localStorage.setItem(props.id, 'true');
|
||||
setDismissed(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
variant={props.variant}
|
||||
dismissible
|
||||
icon={props.icon}
|
||||
onClose={onClose}
|
||||
show={dismissed}
|
||||
>
|
||||
<Alert.Heading>{props.header}</Alert.Heading>
|
||||
<p>
|
||||
{props.body}
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
OneTimeDismissibleAlert.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
variant: PropTypes.string,
|
||||
icon: PropTypes.func,
|
||||
header: PropTypes.string,
|
||||
body: PropTypes.string,
|
||||
};
|
||||
|
||||
OneTimeDismissibleAlert.defaultProps = {
|
||||
variant: 'success',
|
||||
icon: undefined,
|
||||
header: undefined,
|
||||
body: undefined,
|
||||
};
|
||||
|
||||
export default OneTimeDismissibleAlert;
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TransitionReplace } from '@edx/paragon';
|
||||
|
||||
|
||||
const onChildExit = (htmlNode) => {
|
||||
// If the leaving child has focus, take control and redirect it
|
||||
if (htmlNode.contains(document.activeElement)) {
|
||||
@@ -11,7 +10,9 @@ const onChildExit = (htmlNode) => {
|
||||
const enteringChild = htmlNode.previousSibling || htmlNode.nextSibling;
|
||||
|
||||
// There's no replacement, do nothing.
|
||||
if (!enteringChild) return;
|
||||
if (!enteringChild) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all the focusable elements in the entering child and focus the first one
|
||||
const focusableElements = enteringChild.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
@@ -21,15 +22,15 @@ const onChildExit = (htmlNode) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function SwitchContent({ expression, cases, className }) {
|
||||
const SwitchContent = ({ expression, cases, className }) => {
|
||||
const getContent = (caseKey) => {
|
||||
if (cases[caseKey]) {
|
||||
if (typeof cases[caseKey] === 'string') {
|
||||
return getContent(cases[caseKey]);
|
||||
}
|
||||
return React.cloneElement(cases[caseKey], { key: caseKey });
|
||||
} else if (cases.default) {
|
||||
}
|
||||
if (cases.default) {
|
||||
if (typeof cases.default === 'string') {
|
||||
return getContent(cases.default);
|
||||
}
|
||||
@@ -47,8 +48,7 @@ function SwitchContent({ expression, cases, className }) {
|
||||
{getContent(expression)}
|
||||
</TransitionReplace>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
SwitchContent.propTypes = {
|
||||
expression: PropTypes.string,
|
||||
@@ -61,5 +61,4 @@ SwitchContent.defaultProps = {
|
||||
className: null,
|
||||
};
|
||||
|
||||
|
||||
export default SwitchContent;
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h6, .h6 {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
line-height: 1.2;
|
||||
border: none;
|
||||
@@ -12,36 +14,33 @@
|
||||
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;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
max-width: 500px;
|
||||
|
||||
.custom-control-label {
|
||||
left: 2.25rem;
|
||||
line-height: 1.6rem;
|
||||
}
|
||||
}
|
||||
.grayed-out{
|
||||
opacity: 0.6; /* Real browsers */
|
||||
filter: alpha(opacity = 60); /* MSIE */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
ActionRow,
|
||||
Form,
|
||||
ModalDialog,
|
||||
StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import {
|
||||
closeForm,
|
||||
resetDrafts,
|
||||
saveSettings,
|
||||
updateDraft,
|
||||
} from '../data/actions';
|
||||
import { certPreferenceSelector } from '../data/selectors';
|
||||
|
||||
import commonMessages from '../AccountSettingsPage.messages';
|
||||
import messages from './messages';
|
||||
|
||||
const CertificatePreference = ({
|
||||
intl,
|
||||
fieldName,
|
||||
originalFullName,
|
||||
originalVerifiedName,
|
||||
saveState,
|
||||
useVerifiedNameForCerts,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||
const formId = 'useVerifiedNameForCerts';
|
||||
|
||||
const handleCheckboxChange = () => {
|
||||
if (!checked) {
|
||||
if (fieldName === 'verified_name') {
|
||||
dispatch(updateDraft(formId, true));
|
||||
} else {
|
||||
dispatch(updateDraft(formId, false));
|
||||
}
|
||||
} else {
|
||||
setModalIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setModalIsOpen(false);
|
||||
dispatch(resetDrafts());
|
||||
};
|
||||
|
||||
const handleModalChange = (e) => {
|
||||
if (e.target.value === 'fullName') {
|
||||
dispatch(updateDraft(formId, false));
|
||||
} else {
|
||||
dispatch(updateDraft(formId, true));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (saveState === 'pending') {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(saveSettings(formId, useVerifiedNameForCerts));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (originalVerifiedName) {
|
||||
if (fieldName === 'verified_name') {
|
||||
setChecked(useVerifiedNameForCerts);
|
||||
} else {
|
||||
setChecked(!useVerifiedNameForCerts);
|
||||
}
|
||||
}
|
||||
}, [originalVerifiedName, fieldName, useVerifiedNameForCerts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (originalVerifiedName) {
|
||||
if (modalIsOpen && saveState === 'complete') {
|
||||
setModalIsOpen(false);
|
||||
dispatch(closeForm(fieldName));
|
||||
}
|
||||
}
|
||||
}, [dispatch, originalVerifiedName, fieldName, modalIsOpen, saveState]);
|
||||
|
||||
// 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'])}
|
||||
</Form.Checkbox>
|
||||
|
||||
<ModalDialog
|
||||
title={intl.formatMessage(messages['account.settings.field.name.modal.certificate.title'])}
|
||||
isOpen={modalIsOpen}
|
||||
onClose={handleCancel}
|
||||
size="lg"
|
||||
hasCloseButton
|
||||
isFullscreenOnMobile
|
||||
>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
{intl.formatMessage(messages['account.settings.field.name.modal.certificate.title'])}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
|
||||
<ModalDialog.Body className="overflow-hidden">
|
||||
<Form.Group className="mb-4">
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages['account.settings.field.name.modal.certificate.select'])}
|
||||
</Form.Label>
|
||||
<Form.RadioSet
|
||||
name={formId}
|
||||
value={useVerifiedNameForCerts ? 'verifiedName' : 'fullName'}
|
||||
onChange={handleModalChange}
|
||||
>
|
||||
<Form.Radio value="fullName">
|
||||
{originalFullName}{' '}
|
||||
({intl.formatMessage(messages['account.settings.field.name.modal.certificate.option.full'])})
|
||||
</Form.Radio>
|
||||
<Form.Radio value="verifiedName">
|
||||
{originalVerifiedName}{' '}
|
||||
({intl.formatMessage(messages['account.settings.field.name.modal.certificate.option.verified'])})
|
||||
</Form.Radio>
|
||||
</Form.RadioSet>
|
||||
</Form.Group>
|
||||
</ModalDialog.Body>
|
||||
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="outline-primary" disabled={saveState === 'pending'}>
|
||||
{intl.formatMessage(commonMessages['account.settings.editable.field.action.cancel'])}
|
||||
</ModalDialog.CloseButton>
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
state={saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.field.name.modal.certificate.button.choose']),
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</Form>
|
||||
</ModalDialog>
|
||||
</>
|
||||
) : null;
|
||||
};
|
||||
|
||||
CertificatePreference.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
fieldName: PropTypes.string.isRequired,
|
||||
originalFullName: PropTypes.string,
|
||||
originalVerifiedName: PropTypes.string,
|
||||
saveState: PropTypes.string,
|
||||
useVerifiedNameForCerts: PropTypes.bool,
|
||||
};
|
||||
|
||||
CertificatePreference.defaultProps = {
|
||||
originalFullName: '',
|
||||
originalVerifiedName: '',
|
||||
saveState: null,
|
||||
useVerifiedNameForCerts: false,
|
||||
};
|
||||
|
||||
export default connect(certPreferenceSelector)(injectIntl(CertificatePreference));
|
||||
22
src/account-settings/certificate-preference/data/service.js
Normal file
22
src/account-settings/certificate-preference/data/service.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { handleRequestError } from '../../data/utils';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function postVerifiedNameConfig(username, commitValues) {
|
||||
const requestConfig = { headers: { Accept: 'application/json' } };
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name/config`;
|
||||
|
||||
const { useVerifiedNameForCerts } = commitValues;
|
||||
const postValues = {
|
||||
username,
|
||||
use_verified_name_for_certs: useVerifiedNameForCerts,
|
||||
};
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(requestUrl, postValues, requestConfig)
|
||||
.catch(error => handleRequestError(error));
|
||||
|
||||
return data;
|
||||
}
|
||||
36
src/account-settings/certificate-preference/messages.js
Normal file
36
src/account-settings/certificate-preference/messages.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'account.settings.field.name.checkbox.certificate.select': {
|
||||
id: 'account.settings.field.name.certificate.select',
|
||||
defaultMessage: 'If checked, this name will appear on your certificates and public-facing records.',
|
||||
description: 'Label for checkbox describing that the selected name will appear on the user‘s certificates.',
|
||||
},
|
||||
'account.settings.field.name.modal.certificate.title': {
|
||||
id: 'account.settings.field.name.modal.certificate.title',
|
||||
defaultMessage: 'Choose a preferred name for certificates and public-facing records',
|
||||
description: 'Title instructing the user to choose a preferred name.',
|
||||
},
|
||||
'account.settings.field.name.modal.certificate.select': {
|
||||
id: 'account.settings.field.name.modal.certificate.select',
|
||||
defaultMessage: 'Select a name',
|
||||
description: 'Label instructing the user to select a name.',
|
||||
},
|
||||
'account.settings.field.name.modal.certificate.option.full': {
|
||||
id: 'account.settings.field.name.modal.certificate.option.full',
|
||||
defaultMessage: 'Full Name',
|
||||
description: 'Option representing the user’s full name.',
|
||||
},
|
||||
'account.settings.field.name.modal.certificate.option.verified': {
|
||||
id: 'account.settings.field.name.modal.certificate.option.verified',
|
||||
defaultMessage: 'Verified Name',
|
||||
description: 'Option representing the user’s verified name.',
|
||||
},
|
||||
'account.settings.field.name.modal.certificate.button.choose': {
|
||||
id: 'account.settings.field.name.modal.certificate.button.choose',
|
||||
defaultMessage: 'Choose name',
|
||||
description: 'Button to confirm the user’s name choice.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -0,0 +1,174 @@
|
||||
/* 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 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';
|
||||
|
||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||
ReactDOM.createPortal = node => node;
|
||||
|
||||
import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first
|
||||
|
||||
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 history = createMemoryHistory();
|
||||
|
||||
const IntlCertificatePreference = injectIntl(CertificatePreference);
|
||||
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('NameChange', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
const formId = 'useVerifiedNameForCerts';
|
||||
const updateDraft = 'UPDATE_DRAFT';
|
||||
const labelText = 'If checked, this name will appear on your certificates and public-facing records.';
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore();
|
||||
props = {
|
||||
fieldName: 'name',
|
||||
originalFullName: 'Ed X',
|
||||
originalVerifiedName: 'edX Verified',
|
||||
saveState: null,
|
||||
useVerifiedNameForCerts: false,
|
||||
intl: {},
|
||||
};
|
||||
|
||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||
patch: async () => ({
|
||||
data: { status: 200 },
|
||||
catch: () => {},
|
||||
}),
|
||||
}));
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 }));
|
||||
});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
it('does not render if there is no verified name', () => {
|
||||
props = {
|
||||
...props,
|
||||
originalVerifiedName: '',
|
||||
};
|
||||
|
||||
const wrapper = render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does not trigger modal when checking empty checkbox, and updates draft immediately', () => {
|
||||
props = {
|
||||
...props,
|
||||
useVerifiedNameForCerts: true,
|
||||
};
|
||||
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
expect(checkbox.checked).toEqual(false);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
expect(screen.queryByRole('radiogroup')).toBeNull();
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
payload: { name: formId, value: false },
|
||||
type: updateDraft,
|
||||
});
|
||||
});
|
||||
|
||||
it('triggers modal when attempting to uncheck checkbox', () => {
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
expect(checkbox.checked).toEqual(true);
|
||||
|
||||
fireEvent.click(checkbox);
|
||||
expect(mockDispatch).not.toHaveBeenCalled();
|
||||
|
||||
screen.getByRole('radiogroup');
|
||||
});
|
||||
|
||||
it('updates draft when changing radio value', () => {
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
const fullNameOption = screen.getByLabelText('Ed X (Full Name)');
|
||||
const verifiedNameOption = screen.getByLabelText('edX Verified (Verified Name)');
|
||||
expect(fullNameOption.checked).toEqual(true);
|
||||
expect(verifiedNameOption.checked).toEqual(false);
|
||||
|
||||
fireEvent.click(verifiedNameOption);
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
payload: { name: formId, value: true },
|
||||
type: updateDraft,
|
||||
});
|
||||
});
|
||||
|
||||
it('clears draft on cancel', () => {
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
const cancelButton = screen.getByText('Cancel');
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(mockDispatch).toHaveBeenCalledWith({ type: 'RESET_DRAFTS' });
|
||||
expect(screen.queryByRole('radiogroup')).toBeNull();
|
||||
});
|
||||
|
||||
it('submits', () => {
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
fireEvent.click(checkbox);
|
||||
|
||||
const submitButton = screen.getByText('Choose name');
|
||||
fireEvent.click(submitButton);
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
payload: { formId, commitValues: false },
|
||||
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
||||
});
|
||||
});
|
||||
|
||||
it('checks box for verified name', () => {
|
||||
props = {
|
||||
...props,
|
||||
fieldName: 'verified_name',
|
||||
useVerifiedNameForCerts: true,
|
||||
};
|
||||
|
||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
||||
|
||||
const checkbox = screen.getByLabelText(labelText);
|
||||
expect(checkbox.checked).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`NameChange does not render if there is no verified name 1`] = `
|
||||
Object {
|
||||
"asFragment": [Function],
|
||||
"baseElement": <body>
|
||||
<div />
|
||||
</body>,
|
||||
"container": <div />,
|
||||
"debug": [Function],
|
||||
"findAllByAltText": [Function],
|
||||
"findAllByDisplayValue": [Function],
|
||||
"findAllByLabelText": [Function],
|
||||
"findAllByPlaceholderText": [Function],
|
||||
"findAllByRole": [Function],
|
||||
"findAllByTestId": [Function],
|
||||
"findAllByText": [Function],
|
||||
"findAllByTitle": [Function],
|
||||
"findByAltText": [Function],
|
||||
"findByDisplayValue": [Function],
|
||||
"findByLabelText": [Function],
|
||||
"findByPlaceholderText": [Function],
|
||||
"findByRole": [Function],
|
||||
"findByTestId": [Function],
|
||||
"findByText": [Function],
|
||||
"findByTitle": [Function],
|
||||
"getAllByAltText": [Function],
|
||||
"getAllByDisplayValue": [Function],
|
||||
"getAllByLabelText": [Function],
|
||||
"getAllByPlaceholderText": [Function],
|
||||
"getAllByRole": [Function],
|
||||
"getAllByTestId": [Function],
|
||||
"getAllByText": [Function],
|
||||
"getAllByTitle": [Function],
|
||||
"getByAltText": [Function],
|
||||
"getByDisplayValue": [Function],
|
||||
"getByLabelText": [Function],
|
||||
"getByPlaceholderText": [Function],
|
||||
"getByRole": [Function],
|
||||
"getByTestId": [Function],
|
||||
"getByText": [Function],
|
||||
"getByTitle": [Function],
|
||||
"queryAllByAltText": [Function],
|
||||
"queryAllByDisplayValue": [Function],
|
||||
"queryAllByLabelText": [Function],
|
||||
"queryAllByPlaceholderText": [Function],
|
||||
"queryAllByRole": [Function],
|
||||
"queryAllByTestId": [Function],
|
||||
"queryAllByText": [Function],
|
||||
"queryAllByTitle": [Function],
|
||||
"queryByAltText": [Function],
|
||||
"queryByDisplayValue": [Function],
|
||||
"queryByLabelText": [Function],
|
||||
"queryByPlaceholderText": [Function],
|
||||
"queryByRole": [Function],
|
||||
"queryByTestId": [Function],
|
||||
"queryByText": [Function],
|
||||
"queryByTitle": [Function],
|
||||
"rerender": [Function],
|
||||
"unmount": [Function],
|
||||
}
|
||||
`;
|
||||
@@ -7,21 +7,19 @@ 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, saveSettings, saveMultipleSettings } from '../data/actions';
|
||||
import { fetchSettings } from '../data/actions';
|
||||
import { coachingConsentPageSelector } from '../data/selectors';
|
||||
|
||||
const Logo = ({ src, alt, ...attributes }) => (
|
||||
<>
|
||||
<img src={src} alt={alt} {...attributes} />
|
||||
</>
|
||||
);
|
||||
const Logo = ({ src, alt, ...attributes }) => <img src={src} alt={alt} {...attributes} />;
|
||||
|
||||
const SuccessMessage = props => (
|
||||
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>
|
||||
@@ -34,6 +32,7 @@ const SuccessMessage = props => (
|
||||
|
||||
const AutoRedirect = (props) => {
|
||||
window.location.href = props.redirectUrl;
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
};
|
||||
|
||||
@@ -57,60 +56,33 @@ class CoachingConsent extends React.Component {
|
||||
formErrors: {},
|
||||
formSubmitted: false,
|
||||
declineSubmitted: false,
|
||||
allSubmissionsComplete: 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();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
/*
|
||||
When we are submitting the form, we're calling saveSettings 3 times, which causes
|
||||
multiple parallel redux flows. Because of this we can't rely on just the redux states
|
||||
being sent in through props. For instance if the coaching submission and name
|
||||
submission happen in near parallel, the coaching flow could return errors in
|
||||
formErrors and the name flow could overwrite the formErrors with an empty object.
|
||||
|
||||
To minimize disruption to the rest of the app, we're going to manage flow state from
|
||||
within this component.
|
||||
*/
|
||||
|
||||
// If a new error comes in, store it before the next redux call overwrites it.
|
||||
let allFormErrors = {};
|
||||
let allSubmissionsComplete = false;
|
||||
|
||||
// Collect new errors and add to state (will be cleared on new submission)
|
||||
const newErrorsFound = (
|
||||
this.props.formErrors !== prevProps.formErrors
|
||||
&& Object.keys(this.props.formErrors).length > 0
|
||||
);
|
||||
if (newErrorsFound) {
|
||||
allFormErrors = Object.assign({}, this.state.formErrors, this.props.formErrors);
|
||||
}
|
||||
|
||||
// Check if all values from the form have confirmation values
|
||||
if (
|
||||
this.state.formSubmitted &&
|
||||
this.props.saveState === 'complete'
|
||||
) {
|
||||
allSubmissionsComplete = true;
|
||||
}
|
||||
|
||||
// Check if all values from the decline link have confirmation values
|
||||
if (this.props.confirmationValues.coaching && this.state.declineSubmitted) {
|
||||
allSubmissionsComplete = true;
|
||||
}
|
||||
if (newErrorsFound || (allSubmissionsComplete !== prevState.allSubmissionsComplete)) {
|
||||
this.setState({
|
||||
formErrors: allFormErrors,
|
||||
allSubmissionsComplete,
|
||||
});
|
||||
}
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const fullName = e.target.fullName.value;
|
||||
const phoneNumber = e.target.phoneNumber.value;
|
||||
const body = {
|
||||
coaching_consent: true,
|
||||
consent_form_seen: true,
|
||||
phone_number: phoneNumber,
|
||||
full_name: fullName,
|
||||
};
|
||||
this.setState({
|
||||
formErrors: {},
|
||||
formSubmitted: true,
|
||||
declineSubmitted: false,
|
||||
}, () => this.patchUsingCoachingConsentForm(body));
|
||||
}
|
||||
|
||||
sanitizeForwardingUrl(url) {
|
||||
@@ -118,60 +90,40 @@ class CoachingConsent extends React.Component {
|
||||
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
formErrors: {},
|
||||
formSubmitted: true,
|
||||
declineSubmitted: false,
|
||||
});
|
||||
// Must store target values or they disappear before the async function can use them.
|
||||
const fullName = e.target.fullName.value;
|
||||
const phoneNumber = e.target.phoneNumber.value;
|
||||
const coachingValues = this.props.formValues.coaching;
|
||||
|
||||
// !important: The order of this data matters!
|
||||
// The order that this data is in, is the order that the saveSettings() function
|
||||
// is called.
|
||||
const settingsSubmissions = [];
|
||||
if (!this.props.profileDataManager) {
|
||||
settingsSubmissions.push({
|
||||
formId: 'name',
|
||||
commitValues: fullName,
|
||||
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 });
|
||||
}
|
||||
Array.prototype.push.apply(settingsSubmissions, [
|
||||
{
|
||||
formId: 'coaching',
|
||||
commitValues: {
|
||||
...coachingValues,
|
||||
phone_number: phoneNumber,
|
||||
coaching_consent: true,
|
||||
consent_form_seen: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
formId: 'phone_number',
|
||||
commitValues: phoneNumber,
|
||||
},
|
||||
]);
|
||||
this.props.saveMultipleSettings(settingsSubmissions);
|
||||
}
|
||||
|
||||
async declineCoaching(e) {
|
||||
declineCoaching(e) {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
formErrors: {},
|
||||
declineSubmitted: true,
|
||||
formSubmitted: false,
|
||||
});
|
||||
// Must store target values or they disappear before the async function can use them.
|
||||
const coachingValues = this.props.formValues.coaching;
|
||||
this.props.saveSettings('coaching', {
|
||||
...coachingValues,
|
||||
const body = {
|
||||
coaching_consent: false,
|
||||
consent_form_seen: true,
|
||||
});
|
||||
};
|
||||
this.setState({
|
||||
formErrors: {},
|
||||
formSubmitted: false,
|
||||
declineSubmitted: true,
|
||||
}, () => this.patchUsingCoachingConsentForm(body));
|
||||
}
|
||||
|
||||
renderView(currentView) {
|
||||
@@ -179,28 +131,33 @@ class CoachingConsent extends React.Component {
|
||||
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}
|
||||
/>);
|
||||
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'])}
|
||||
/>);
|
||||
return (
|
||||
<SuccessMessage
|
||||
continueUrl={this.state.redirectUrl}
|
||||
header={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.header'])}
|
||||
message={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.message'])}
|
||||
continue={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.continue'])}
|
||||
/>
|
||||
);
|
||||
case VIEWS.DECLINE_PENDING:
|
||||
return <PageLoading srMessage="Redirecting..." />;
|
||||
case VIEWS.DECLINED:
|
||||
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
|
||||
default:
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
@@ -213,13 +170,13 @@ class CoachingConsent extends React.Component {
|
||||
if (!loaded) {
|
||||
currentView = VIEWS.NOT_LOADED;
|
||||
} else if (this.state.formSubmitted && !formHasErrors) {
|
||||
if (this.state.allSubmissionsComplete) {
|
||||
if (this.state.submissionSuccess) {
|
||||
currentView = VIEWS.SUCCESS;
|
||||
} else {
|
||||
currentView = VIEWS.SUCCESS_PENDING;
|
||||
}
|
||||
} else if (this.state.declineSubmitted && !formHasErrors) {
|
||||
if (this.state.allSubmissionsComplete) {
|
||||
if (this.state.submissionSuccess) {
|
||||
currentView = VIEWS.DECLINED;
|
||||
} else {
|
||||
currentView = VIEWS.DECLINE_PENDING;
|
||||
@@ -277,7 +234,6 @@ AutoRedirect.propTypes = {
|
||||
|
||||
CoachingConsent.defaultProps = {
|
||||
loaded: false,
|
||||
saveState: undefined,
|
||||
profileDataManager: null,
|
||||
};
|
||||
|
||||
@@ -295,22 +251,17 @@ CoachingConsent.propTypes = {
|
||||
}),
|
||||
}).isRequired,
|
||||
formErrors: PropTypes.shape({
|
||||
coaching: PropTypes.object,
|
||||
coaching: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
confirmationValues: PropTypes.shape({
|
||||
coaching: PropTypes.object,
|
||||
name: PropTypes.object,
|
||||
phone_number: PropTypes.object,
|
||||
coaching: PropTypes.shape({}),
|
||||
name: PropTypes.shape({}),
|
||||
phone_number: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
fetchSettings: PropTypes.func.isRequired,
|
||||
saveSettings: PropTypes.func.isRequired,
|
||||
saveMultipleSettings: PropTypes.func.isRequired,
|
||||
saveState: PropTypes.string,
|
||||
profileDataManager: PropTypes.string,
|
||||
};
|
||||
|
||||
export default connect(coachingConsentPageSelector, {
|
||||
fetchSettings,
|
||||
saveSettings,
|
||||
saveMultipleSettings,
|
||||
})(injectIntl(CoachingConsent));
|
||||
|
||||
@@ -48,7 +48,7 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.coaching.consent.success.message': {
|
||||
id: 'account.settings.coaching.consent.success.message',
|
||||
defaultMessage: "You're signed up for coaching. You will receive a text message confirmation.",
|
||||
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': {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Input, Button, Hyperlink } from '@edx/paragon';
|
||||
|
||||
@@ -9,23 +8,21 @@ 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 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"
|
||||
description="Alert message informing the user their account data is managed by a third party"
|
||||
values={{
|
||||
managerTitle: <b>{profileDataManager}</b>,
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
);
|
||||
const CoachingForm = props => (
|
||||
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'])}
|
||||
@@ -34,12 +31,13 @@ const CoachingForm = props => (
|
||||
<div>
|
||||
<form onSubmit={props.onSubmit}>
|
||||
<div className="py-3">
|
||||
{
|
||||
!!props.profileDataManager &&
|
||||
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
|
||||
}
|
||||
<ErrorMessage message={props.formErrors.name} />
|
||||
<label className="h6" htmlFor="fullName">{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}</label>
|
||||
{!!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"
|
||||
@@ -50,10 +48,12 @@ const CoachingForm = props => (
|
||||
</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>
|
||||
<label className="h6" htmlFor="phoneNumber">
|
||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
name="full-name"
|
||||
name="phone_number"
|
||||
id="phoneNumber"
|
||||
defaultValue={props.formValues.phone_number}
|
||||
/>
|
||||
@@ -65,7 +65,7 @@ const CoachingForm = props => (
|
||||
</div>
|
||||
<ErrorMessage message={props.formErrors.coaching} />
|
||||
<div className="d-flex flex-column align-items-center">
|
||||
<Button className="w-100 btn-outline-primary" type="submit">
|
||||
<Button variant="outline-primary" className="w-100" type="submit">
|
||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -107,7 +107,7 @@ CoachingForm.propTypes = {
|
||||
}).isRequired,
|
||||
formErrors: PropTypes.shape({
|
||||
coaching: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
full_name: PropTypes.string,
|
||||
phone_number: PropTypes.string,
|
||||
}),
|
||||
redirectUrl: PropTypes.string.isRequired,
|
||||
@@ -124,7 +124,6 @@ ErrorMessage.propTypes = {
|
||||
|
||||
ManagedProfileAlert.propTypes = {
|
||||
profileDataManager: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(CoachingForm);
|
||||
|
||||
@@ -8,8 +8,7 @@ import { editableFieldSelector } from '../data/selectors';
|
||||
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
|
||||
import EditableField from '../EditableField';
|
||||
|
||||
|
||||
const CoachingToggle = props => (
|
||||
const CoachingToggle = (props) => (
|
||||
<>
|
||||
<EditableField
|
||||
name="phone_number"
|
||||
@@ -33,10 +32,10 @@ const CoachingToggle = props => (
|
||||
formId: 'phone_number',
|
||||
commitValues: props.phone_number,
|
||||
},
|
||||
]);
|
||||
}
|
||||
], 'phone_number');
|
||||
}
|
||||
return props.saveSettings('phone_number', props.phone_number);
|
||||
}}
|
||||
}}
|
||||
/>
|
||||
<ValidationFormGroup
|
||||
for="coachingConsent"
|
||||
@@ -59,6 +58,7 @@ const CoachingToggle = props => (
|
||||
const { user, eligible_for_coaching } = props.coaching;
|
||||
const value = {
|
||||
user,
|
||||
// eslint-disable-next-line camelcase
|
||||
eligible_for_coaching,
|
||||
coaching_consent: e.target.checked,
|
||||
};
|
||||
|
||||
103
src/account-settings/coaching/test/CoachingConsent.test.jsx
Normal file
103
src/account-settings/coaching/test/CoachingConsent.test.jsx
Normal file
@@ -0,0 +1,103 @@
|
||||
/* eslint-disable no-import-assign */
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
|
||||
import CoachingConsent from '../CoachingConsent';
|
||||
import * as selectors from '../../data/selectors';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
|
||||
const IntlCoachingConsent = injectIntl(CoachingConsent);
|
||||
|
||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ coachingConsentPageSelector: () => ({}) })));
|
||||
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('CoachingConsent', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
selectors.mockClear();
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore();
|
||||
props = {
|
||||
fetchSettings: jest.fn(),
|
||||
loaded: true,
|
||||
saveState: undefined,
|
||||
formValues: {
|
||||
name: 'edx edx',
|
||||
phone_number: '1234567890',
|
||||
coaching: {
|
||||
coaching_consent: true,
|
||||
consent_form_seen: false,
|
||||
eligible_for_coaching: true,
|
||||
user: 1,
|
||||
},
|
||||
},
|
||||
formErrors: {},
|
||||
confirmationValues: {},
|
||||
profileDataManager: '',
|
||||
intl: {},
|
||||
};
|
||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||
patch: async () => ({
|
||||
data: { status: 200 },
|
||||
catch: () => {},
|
||||
}),
|
||||
}));
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 }));
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('disables name field on enterprise user', () => {
|
||||
props = {
|
||||
...props,
|
||||
profileDataManager: 'test person',
|
||||
};
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('display completed box when successfully submitted', async () => {
|
||||
const fakeEvent = {
|
||||
preventDefault: () => {},
|
||||
target: {
|
||||
fullName: { value: 'edx edx' },
|
||||
phoneNumber: { value: '9783028731' },
|
||||
},
|
||||
};
|
||||
const wrapper = renderer.create(
|
||||
reduxWrapper(<IntlCoachingConsent {...props} />),
|
||||
{
|
||||
// bypass the forward-ref. we don't care about focus for this one test
|
||||
createNodeMock: (element) => {
|
||||
if (element.type === 'button') {
|
||||
// mock a focus function
|
||||
return {
|
||||
focus: async () => wrapper.root.findByType('form').props.onSubmit(fakeEvent),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
);
|
||||
const form = wrapper.root.findByType('form');
|
||||
await act(async () => { await form.props.onSubmit(fakeEvent); });
|
||||
expect(wrapper.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,276 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CoachingConsent disables name field on enterprise user 1`] = `
|
||||
<main>
|
||||
<div
|
||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||
>
|
||||
<img
|
||||
alt="Logo"
|
||||
className="logo"
|
||||
src="icon/mock/path"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
||||
>
|
||||
<h2
|
||||
className="h2"
|
||||
>
|
||||
Let’s get started.
|
||||
</h2>
|
||||
<p>
|
||||
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
||||
</p>
|
||||
<div>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="py-3"
|
||||
>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert alert-primary"
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
Your name is managed by
|
||||
<b>
|
||||
test person
|
||||
</b>
|
||||
. Contact your administrator for help.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="alert-warning mb-2"
|
||||
>
|
||||
|
||||
</div>
|
||||
<label
|
||||
className="h6"
|
||||
htmlFor="fullName"
|
||||
>
|
||||
Please confirm your name
|
||||
</label>
|
||||
<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="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
I prefer not to be contacted with free coaching services
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
|
||||
exports[`CoachingConsent display completed box when successfully submitted 1`] = `
|
||||
<main>
|
||||
<div
|
||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||
>
|
||||
<img
|
||||
alt="Logo"
|
||||
className="logo"
|
||||
src="icon/mock/path"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className="d-flex justify-content-center align-items-center flex-column"
|
||||
style={
|
||||
Object {
|
||||
"height": "50vh",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="spinner-border text-primary"
|
||||
role="status"
|
||||
>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
Submitting...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
|
||||
exports[`CoachingConsent should render 1`] = `
|
||||
<main>
|
||||
<div
|
||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
||||
>
|
||||
<img
|
||||
alt="Logo"
|
||||
className="logo"
|
||||
src="icon/mock/path"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
||||
>
|
||||
<h2
|
||||
className="h2"
|
||||
>
|
||||
Let’s get started.
|
||||
</h2>
|
||||
<p>
|
||||
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
||||
</p>
|
||||
<div>
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="py-3"
|
||||
>
|
||||
<div
|
||||
className="alert-warning mb-2"
|
||||
>
|
||||
|
||||
</div>
|
||||
<label
|
||||
className="h6"
|
||||
htmlFor="fullName"
|
||||
>
|
||||
Please confirm your name
|
||||
</label>
|
||||
<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="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
||||
href="http://localhost:18000/dashboard/"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
I prefer not to be contacted with free coaching services
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
`;
|
||||
@@ -9,6 +9,7 @@ export const OPEN_FORM = 'OPEN_FORM';
|
||||
export const CLOSE_FORM = 'CLOSE_FORM';
|
||||
export const UPDATE_DRAFT = 'UPDATE_DRAFT';
|
||||
export const RESET_DRAFTS = 'RESET_DRAFTS';
|
||||
export const BEGIN_NAME_CHANGE = 'BEGIN_NAME_CHANGE';
|
||||
|
||||
// FETCH SETTINGS ACTIONS
|
||||
|
||||
@@ -25,6 +26,7 @@ export const fetchSettingsSuccess = ({
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
verifiedNameHistory,
|
||||
}) => ({
|
||||
type: FETCH_SETTINGS.SUCCESS,
|
||||
payload: {
|
||||
@@ -32,6 +34,7 @@ export const fetchSettingsSuccess = ({
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
verifiedNameHistory,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,7 +47,6 @@ export const fetchSettingsReset = () => ({
|
||||
type: FETCH_SETTINGS.RESET,
|
||||
});
|
||||
|
||||
|
||||
// FORM STATE ACTIONS
|
||||
|
||||
export const openForm = formId => ({
|
||||
@@ -69,7 +71,10 @@ export const resetDrafts = () => ({
|
||||
type: RESET_DRAFTS,
|
||||
});
|
||||
|
||||
|
||||
export const beginNameChange = (formId) => ({
|
||||
type: BEGIN_NAME_CHANGE,
|
||||
payload: { formId },
|
||||
});
|
||||
// SAVE SETTINGS ACTIONS
|
||||
|
||||
export const saveSettings = (formId, commitValues) => ({
|
||||
@@ -100,9 +105,9 @@ export const savePreviousSiteLanguage = previousSiteLanguage => ({
|
||||
payload: { previousSiteLanguage },
|
||||
});
|
||||
|
||||
export const saveMultipleSettings = settingsArray => ({
|
||||
export const saveMultipleSettings = (settingsArray, form = null) => ({
|
||||
type: SAVE_MULTIPLE_SETTINGS.BASE,
|
||||
payload: { settingsArray },
|
||||
payload: { settingsArray, form },
|
||||
});
|
||||
|
||||
export const saveMultipleSettingsBegin = () => ({
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export const YEAR_OF_BIRTH_OPTIONS = (() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [];
|
||||
@@ -11,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',
|
||||
@@ -112,3 +116,7 @@ const COUNTRY_STATES_MAP = {
|
||||
export function getStatesList(country) {
|
||||
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
|
||||
}
|
||||
|
||||
export const DECLINED = 'declined';
|
||||
export const SELF_DESCRIBE = 'self-describe';
|
||||
export const OTHER = 'other';
|
||||
|
||||
@@ -8,11 +8,13 @@ import {
|
||||
UPDATE_DRAFT,
|
||||
RESET_DRAFTS,
|
||||
SAVE_MULTIPLE_SETTINGS,
|
||||
BEGIN_NAME_CHANGE,
|
||||
} from './actions';
|
||||
|
||||
import { reducer as deleteAccountReducer, DELETE_ACCOUNT } from '../delete-account';
|
||||
import { reducer as siteLanguageReducer, FETCH_SITE_LANGUAGES } from '../site-language';
|
||||
import { reducer as resetPasswordReducer, RESET_PASSWORD } from '../reset-password';
|
||||
import { reducer as nameChangeReducer, REQUEST_NAME_CHANGE } from '../name-change';
|
||||
import { reducer as thirdPartyAuthReducer, DISCONNECT_AUTH } from '../third-party-auth';
|
||||
|
||||
export const defaultState = {
|
||||
@@ -31,10 +33,15 @@ export const defaultState = {
|
||||
deleteAccount: deleteAccountReducer(),
|
||||
siteLanguage: siteLanguageReducer(),
|
||||
resetPassword: resetPasswordReducer(),
|
||||
nameChange: nameChangeReducer(),
|
||||
thirdPartyAuth: thirdPartyAuthReducer(),
|
||||
nameChangeModal: false,
|
||||
verifiedName: null,
|
||||
mostRecentVerifiedName: {},
|
||||
verifiedNameHistory: {},
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
const reducer = (state = defaultState, action = {}) => {
|
||||
let dispatcherIsOpenForm;
|
||||
|
||||
switch (action.type) {
|
||||
@@ -48,16 +55,15 @@ const reducer = (state = defaultState, action) => {
|
||||
case FETCH_SETTINGS.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
values: Object.assign({}, state.values, action.payload.values),
|
||||
values: { ...state.values, ...action.payload.values },
|
||||
// Dump the providers into thirdPartyAuth.
|
||||
thirdPartyAuth: Object.assign({}, state.thirdPartyAuth, {
|
||||
providers: action.payload.thirdPartyAuthProviders,
|
||||
}),
|
||||
thirdPartyAuth: { ...state.thirdPartyAuth, providers: action.payload.thirdPartyAuthProviders },
|
||||
profileDataManager: action.payload.profileDataManager,
|
||||
timeZones: action.payload.timeZones,
|
||||
loading: false,
|
||||
loaded: true,
|
||||
loadingError: null,
|
||||
verifiedNameHistory: action.payload.verifiedNameHistory,
|
||||
};
|
||||
case FETCH_SETTINGS.FAILURE:
|
||||
return {
|
||||
@@ -91,15 +97,14 @@ const reducer = (state = defaultState, action) => {
|
||||
saveState: null,
|
||||
errors: {},
|
||||
drafts: {},
|
||||
nameChangeModal: false,
|
||||
};
|
||||
}
|
||||
return state;
|
||||
case UPDATE_DRAFT:
|
||||
return {
|
||||
...state,
|
||||
drafts: Object.assign({}, state.drafts, {
|
||||
[action.payload.name]: action.payload.value,
|
||||
}),
|
||||
drafts: { ...state.drafts, [action.payload.name]: action.payload.value },
|
||||
saveState: null,
|
||||
errors: {},
|
||||
};
|
||||
@@ -110,6 +115,15 @@ const reducer = (state = defaultState, action) => {
|
||||
drafts: {},
|
||||
};
|
||||
|
||||
case BEGIN_NAME_CHANGE:
|
||||
return {
|
||||
...state,
|
||||
saveState: 'error',
|
||||
nameChangeModal: {
|
||||
formId: action.payload.formId,
|
||||
},
|
||||
};
|
||||
|
||||
case SAVE_SETTINGS.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
@@ -120,19 +134,18 @@ const reducer = (state = defaultState, action) => {
|
||||
return {
|
||||
...state,
|
||||
saveState: 'complete',
|
||||
values: Object.assign({}, state.values, action.payload.values),
|
||||
values: { ...state.values, ...action.payload.values },
|
||||
errors: {},
|
||||
confirmationValues: Object.assign(
|
||||
{},
|
||||
state.confirmationValues,
|
||||
action.payload.confirmationValues,
|
||||
),
|
||||
confirmationValues: {
|
||||
...state.confirmationValues,
|
||||
...action.payload.confirmationValues,
|
||||
},
|
||||
};
|
||||
case SAVE_SETTINGS.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
saveState: 'error',
|
||||
errors: Object.assign({}, state.errors, action.payload.errors),
|
||||
errors: { ...state.errors, ...action.payload.errors },
|
||||
};
|
||||
case SAVE_SETTINGS.RESET:
|
||||
return {
|
||||
@@ -161,7 +174,7 @@ const reducer = (state = defaultState, action) => {
|
||||
return {
|
||||
...state,
|
||||
saveState: 'error',
|
||||
errors: Object.assign({}, state.errors, action.payload.errors),
|
||||
errors: { ...state.errors, ...action.payload.errors },
|
||||
};
|
||||
|
||||
case FETCH_TIME_ZONES.SUCCESS:
|
||||
@@ -202,6 +215,15 @@ const reducer = (state = defaultState, action) => {
|
||||
resetPassword: resetPasswordReducer(state.resetPassword, action),
|
||||
};
|
||||
|
||||
case REQUEST_NAME_CHANGE.BEGIN:
|
||||
case REQUEST_NAME_CHANGE.SUCCESS:
|
||||
case REQUEST_NAME_CHANGE.FAILURE:
|
||||
case REQUEST_NAME_CHANGE.RESET:
|
||||
return {
|
||||
...state,
|
||||
nameChange: nameChangeReducer(state.nameChange, action),
|
||||
};
|
||||
|
||||
case DISCONNECT_AUTH.BEGIN:
|
||||
case DISCONNECT_AUTH.SUCCESS:
|
||||
case DISCONNECT_AUTH.FAILURE:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { call, put, delay, takeEvery, all } from 'redux-saga/effects';
|
||||
import {
|
||||
call, put, delay, takeEvery, all,
|
||||
} from 'redux-saga/effects';
|
||||
|
||||
import { publish } from '@edx/frontend-platform';
|
||||
import { getLocale, handleRtl, LOCALE_CHANGED } from '@edx/frontend-platform/i18n';
|
||||
@@ -23,11 +25,13 @@ import {
|
||||
saveMultipleSettingsBegin,
|
||||
saveMultipleSettingsSuccess,
|
||||
saveMultipleSettingsFailure,
|
||||
beginNameChange,
|
||||
} from './actions';
|
||||
|
||||
// Sub-modules
|
||||
import { saga as deleteAccountSaga } from '../delete-account';
|
||||
import { saga as resetPasswordSaga } from '../reset-password';
|
||||
import { saga as nameChangeSaga } from '../name-change';
|
||||
import {
|
||||
saga as siteLanguageSaga,
|
||||
patchPreferences,
|
||||
@@ -36,7 +40,12 @@ import {
|
||||
import { saga as thirdPartyAuthSaga } from '../third-party-auth';
|
||||
|
||||
// Services
|
||||
import { getSettings, patchSettings, getTimeZones } from './service';
|
||||
import {
|
||||
getSettings,
|
||||
patchSettings,
|
||||
getTimeZones,
|
||||
getVerifiedNameHistory,
|
||||
} from './service';
|
||||
|
||||
export function* handleFetchSettings() {
|
||||
try {
|
||||
@@ -52,13 +61,16 @@ export function* handleFetchSettings() {
|
||||
userId,
|
||||
);
|
||||
|
||||
if (values.country) yield put(fetchTimeZones(values.country));
|
||||
const verifiedNameHistory = yield call(getVerifiedNameHistory);
|
||||
|
||||
if (values.country) { yield put(fetchTimeZones(values.country)); }
|
||||
|
||||
yield put(fetchSettingsSuccess({
|
||||
values,
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
verifiedNameHistory,
|
||||
}));
|
||||
} catch (e) {
|
||||
yield put(fetchSettingsFailure(e.message));
|
||||
@@ -91,11 +103,14 @@ export function* handleSaveSettings(action) {
|
||||
savedValues = yield call(patchSettings, username, commitData, userId);
|
||||
}
|
||||
yield put(saveSettingsSuccess(savedValues, commitData));
|
||||
if (savedValues.country) yield put(fetchTimeZones(savedValues.country));
|
||||
if (savedValues.country) { yield put(fetchTimeZones(savedValues.country)); }
|
||||
yield delay(1000);
|
||||
yield put(closeForm(action.payload.formId));
|
||||
} catch (e) {
|
||||
if (e.fieldErrors) {
|
||||
if (e.fieldErrors.name?.includes('verification')) {
|
||||
yield put(beginNameChange('name'));
|
||||
}
|
||||
yield put(saveSettingsFailure({ fieldErrors: e.fieldErrors }));
|
||||
} else {
|
||||
yield put(saveSettingsFailure(e.message));
|
||||
@@ -104,13 +119,12 @@ export function* handleSaveSettings(action) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// handles mutiple settings saved at once, in order, and stops executing on first failure.
|
||||
export function* handleSaveMultipleSettings(settings) {
|
||||
export function* handleSaveMultipleSettings(action) {
|
||||
try {
|
||||
yield put(saveMultipleSettingsBegin());
|
||||
const { username, userId } = getAuthenticatedUser();
|
||||
const { settingsArray } = settings.payload;
|
||||
const { settingsArray, form } = action.payload;
|
||||
for (let i = 0; i < settingsArray.length; i += 1) {
|
||||
const { formId, commitValues } = settingsArray[i];
|
||||
yield put(saveSettingsBegin());
|
||||
@@ -118,9 +132,16 @@ export function* handleSaveMultipleSettings(settings) {
|
||||
const savedSettings = yield call(patchSettings, username, commitData, userId);
|
||||
yield put(saveSettingsSuccess(savedSettings, commitData));
|
||||
}
|
||||
yield put(saveMultipleSettingsSuccess(settings));
|
||||
yield put(saveMultipleSettingsSuccess(action));
|
||||
if (form) {
|
||||
yield delay(1000);
|
||||
yield put(closeForm(form));
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.fieldErrors) {
|
||||
if (e.fieldErrors.name?.includes('verification')) {
|
||||
yield put(beginNameChange('name'));
|
||||
}
|
||||
yield put(saveMultipleSettingsFailure({ fieldErrors: e.fieldErrors }));
|
||||
} else {
|
||||
yield put(saveMultipleSettingsFailure(e.message));
|
||||
@@ -134,7 +155,6 @@ export function* handleFetchTimeZones(action) {
|
||||
yield put(fetchTimeZonesSuccess(response, action.payload.country));
|
||||
}
|
||||
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(FETCH_SETTINGS.BASE, handleFetchSettings);
|
||||
yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings);
|
||||
@@ -144,6 +164,7 @@ export default function* saga() {
|
||||
deleteAccountSaga(),
|
||||
siteLanguageSaga(),
|
||||
resetPasswordSaga(),
|
||||
nameChangeSaga(),
|
||||
thirdPartyAuthSaga(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createSelector, createStructuredSelector } from 'reselect';
|
||||
|
||||
import { siteLanguageOptionsSelector, siteLanguageListSelector } from '../site-language';
|
||||
import { siteLanguageListSelector, siteLanguageOptionsSelector } from '../site-language';
|
||||
import { compareVerifiedNamesByCreatedDate } from '../../utils';
|
||||
|
||||
export const storeName = 'accountSettings';
|
||||
|
||||
@@ -8,9 +8,74 @@ export const accountSettingsSelector = state => ({ ...state[storeName] });
|
||||
|
||||
const editableFieldNameSelector = (state, props) => props.name;
|
||||
|
||||
const verifiedNameSettingsSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => ({
|
||||
history: accountSettings.verifiedNameHistory.results,
|
||||
useVerifiedNameForCerts: accountSettings?.verifiedNameHistory.use_verified_name_for_certs,
|
||||
}),
|
||||
);
|
||||
|
||||
const sortedVerifiedNameHistorySelector = createSelector(
|
||||
verifiedNameSettingsSelector,
|
||||
verifiedNameSettings => {
|
||||
const { history } = verifiedNameSettings;
|
||||
|
||||
if (Array.isArray(history)) {
|
||||
return history.sort(compareVerifiedNamesByCreatedDate);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
);
|
||||
|
||||
const mostRecentVerifiedNameSelector = createSelector(
|
||||
sortedVerifiedNameHistorySelector,
|
||||
sortedHistory => (sortedHistory.length > 0 ? sortedHistory[0] : null),
|
||||
);
|
||||
|
||||
const mostRecentApprovedVerifiedNameValueSelector = createSelector(
|
||||
sortedVerifiedNameHistorySelector,
|
||||
mostRecentVerifiedNameSelector,
|
||||
(sortedHistory, mostRecentVerifiedName) => {
|
||||
const approvedVerifiedNames = sortedHistory.filter(name => name.status === 'approved');
|
||||
const approvedVerifiedName = approvedVerifiedNames.length > 0 ? approvedVerifiedNames[0] : null;
|
||||
|
||||
let verifiedName = null;
|
||||
switch (mostRecentVerifiedName && mostRecentVerifiedName.status) {
|
||||
case 'approved':
|
||||
case 'denied':
|
||||
case 'pending':
|
||||
verifiedName = approvedVerifiedName;
|
||||
break;
|
||||
case 'submitted':
|
||||
verifiedName = mostRecentVerifiedName;
|
||||
break;
|
||||
default:
|
||||
verifiedName = null;
|
||||
}
|
||||
return verifiedName;
|
||||
},
|
||||
);
|
||||
|
||||
const valuesSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => accountSettings.values,
|
||||
mostRecentApprovedVerifiedNameValueSelector,
|
||||
(accountSettings, mostRecentApprovedVerifiedNameValue) => {
|
||||
let useVerifiedNameForCerts = (
|
||||
accountSettings.verifiedNameHistory?.use_verified_name_for_certs || false
|
||||
);
|
||||
|
||||
if (Object.keys(accountSettings.confirmationValues).includes('useVerifiedNameForCerts')) {
|
||||
useVerifiedNameForCerts = accountSettings.confirmationValues.useVerifiedNameForCerts;
|
||||
}
|
||||
|
||||
return {
|
||||
...accountSettings.values,
|
||||
verified_name: mostRecentApprovedVerifiedNameValue?.verified_name,
|
||||
useVerifiedNameForCerts,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const draftsSelector = createSelector(
|
||||
@@ -51,6 +116,11 @@ const errorSelector = createSelector(
|
||||
accountSettings => accountSettings.errors,
|
||||
);
|
||||
|
||||
const nameChangeModalSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => accountSettings.nameChangeModal,
|
||||
);
|
||||
|
||||
const saveStateSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => accountSettings.saveState,
|
||||
@@ -70,9 +140,19 @@ export const profileDataManagerSelector = createSelector(
|
||||
|
||||
export const staticFieldsSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
accountSettings => (accountSettings.profileDataManager ? ['name', 'email', 'country'] : []),
|
||||
);
|
||||
mostRecentVerifiedNameSelector,
|
||||
(accountSettings, verifiedName) => {
|
||||
const staticFields = [];
|
||||
if (accountSettings.profileDataManager) {
|
||||
staticFields.push('name', 'email', 'country');
|
||||
}
|
||||
if (verifiedName && ['submitted'].includes(verifiedName.status)) {
|
||||
staticFields.push('verifiedName');
|
||||
}
|
||||
|
||||
return staticFields;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* If there's no draft present at all (undefined), use the original committed value.
|
||||
@@ -87,7 +167,11 @@ const formValuesSelector = createSelector(
|
||||
(values, drafts) => {
|
||||
const formValues = {};
|
||||
Object.entries(values).forEach(([name, value]) => {
|
||||
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
||||
if (typeof value === 'boolean') {
|
||||
formValues[name] = chooseFormValue(drafts[name], value);
|
||||
} else {
|
||||
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
||||
}
|
||||
});
|
||||
return formValues;
|
||||
},
|
||||
@@ -95,7 +179,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(
|
||||
@@ -132,21 +216,35 @@ export const accountSettingsPageSelector = createSelector(
|
||||
siteLanguageOptionsSelector,
|
||||
siteLanguageSelector,
|
||||
formValuesSelector,
|
||||
valuesSelector,
|
||||
draftsSelector,
|
||||
errorSelector,
|
||||
profileDataManagerSelector,
|
||||
staticFieldsSelector,
|
||||
timeZonesSelector,
|
||||
countryTimeZonesSelector,
|
||||
activeAccountSelector,
|
||||
nameChangeModalSelector,
|
||||
mostRecentApprovedVerifiedNameValueSelector,
|
||||
mostRecentVerifiedNameSelector,
|
||||
sortedVerifiedNameHistorySelector,
|
||||
(
|
||||
accountSettings,
|
||||
siteLanguageOptions,
|
||||
siteLanguage,
|
||||
formValues,
|
||||
committedValues,
|
||||
drafts,
|
||||
formErrors,
|
||||
profileDataManager,
|
||||
staticFields,
|
||||
timeZoneOptions,
|
||||
countryTimeZoneOptions,
|
||||
activeAccount,
|
||||
nameChangeModal,
|
||||
verifiedName,
|
||||
mostRecentVerifiedName,
|
||||
verifiedNameHistory,
|
||||
) => ({
|
||||
siteLanguageOptions,
|
||||
siteLanguage,
|
||||
@@ -157,9 +255,37 @@ export const accountSettingsPageSelector = createSelector(
|
||||
countryTimeZoneOptions,
|
||||
isActive: activeAccount,
|
||||
formValues,
|
||||
committedValues,
|
||||
drafts,
|
||||
formErrors,
|
||||
profileDataManager,
|
||||
staticFields,
|
||||
tpaProviders: accountSettings.thirdPartyAuth.providers,
|
||||
nameChangeModal,
|
||||
verifiedName,
|
||||
mostRecentVerifiedName,
|
||||
verifiedNameHistory,
|
||||
}),
|
||||
);
|
||||
|
||||
export const certPreferenceSelector = createSelector(
|
||||
valuesSelector,
|
||||
formValuesSelector,
|
||||
mostRecentApprovedVerifiedNameValueSelector,
|
||||
saveStateSelector,
|
||||
errorSelector,
|
||||
(
|
||||
committedValues,
|
||||
formValues,
|
||||
mostRecentApprovedVerifiedNameValue,
|
||||
saveState,
|
||||
errors,
|
||||
) => ({
|
||||
originalFullName: committedValues?.name || '',
|
||||
originalVerifiedName: mostRecentApprovedVerifiedNameValue?.verified_name || '',
|
||||
useVerifiedNameForCerts: formValues.useVerifiedNameForCerts || false,
|
||||
saveState,
|
||||
formErrors: errors,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -191,3 +317,27 @@ export const coachingConsentPageSelector = createSelector(
|
||||
formErrors: errors,
|
||||
}),
|
||||
);
|
||||
|
||||
export const demographicsSectionSelector = createSelector(
|
||||
formValuesSelector,
|
||||
draftsSelector,
|
||||
errorSelector,
|
||||
(
|
||||
formValues,
|
||||
drafts,
|
||||
errors,
|
||||
) => ({
|
||||
formValues,
|
||||
drafts,
|
||||
formErrors: errors,
|
||||
}),
|
||||
);
|
||||
|
||||
export const nameChangeSelector = createSelector(
|
||||
accountSettingsSelector,
|
||||
formValuesSelector,
|
||||
(accountSettings, formValues) => ({
|
||||
...accountSettings.nameChange,
|
||||
formValues,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import pick from 'lodash.pick';
|
||||
import pickBy from 'lodash.pickby';
|
||||
import omit from 'lodash.omit';
|
||||
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';
|
||||
|
||||
const SOCIAL_PLATFORMS = [
|
||||
{ id: 'twitter', key: 'social_link_twitter' },
|
||||
@@ -41,7 +45,9 @@ function packAccountCommitData(commitData) {
|
||||
|
||||
SOCIAL_PLATFORMS.forEach(({ id, key }) => {
|
||||
// Skip missing values. Empty strings are valid values and should be preserved.
|
||||
if (commitData[key] === undefined) return;
|
||||
if (commitData[key] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
packedData.social_links = [{ platform: id, social_link: commitData[key] }];
|
||||
delete packedData[key];
|
||||
@@ -149,27 +155,100 @@ export async function getProfileDataManager(username, userRoles) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to determine if the Demographics questions should be displayed to the user. For the
|
||||
* MVP release of Demographics we are limiting the Demographics question visibility only to
|
||||
* MicroBachelors learners.
|
||||
*/
|
||||
export async function shouldDisplayDemographicsQuestions() {
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/demographics/v1/demographics/status/`;
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
({ data } = await getAuthenticatedHttpClient().get(requestUrl));
|
||||
if (data.display) {
|
||||
return data.display;
|
||||
}
|
||||
} catch (error) {
|
||||
// if there was an error then we just hide the section
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function getVerifiedName() {
|
||||
let data;
|
||||
const client = getAuthenticatedHttpClient();
|
||||
try {
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name`;
|
||||
({ data } = await client.get(requestUrl));
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function getVerifiedNameHistory() {
|
||||
let data;
|
||||
const client = getAuthenticatedHttpClient();
|
||||
try {
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name/history`;
|
||||
({ data } = await client.get(requestUrl));
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function postVerifiedName(data) {
|
||||
const requestConfig = { headers: { Accept: 'application/json' } };
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name`;
|
||||
|
||||
await getAuthenticatedHttpClient()
|
||||
.post(requestUrl, data, requestConfig)
|
||||
.catch(error => handleRequestError(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* A single function to GET everything considered a setting.
|
||||
* Currently encapsulates Account, Preferences, Coaching, and ThirdPartyAuth
|
||||
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
|
||||
*/
|
||||
export async function getSettings(username, userRoles, userId) {
|
||||
const results = await Promise.all([
|
||||
const [
|
||||
account,
|
||||
preferences,
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
coaching,
|
||||
shouldDisplayDemographicsQuestionsResponse,
|
||||
demographics,
|
||||
demographicsOptions,
|
||||
] = await Promise.all([
|
||||
getAccount(username),
|
||||
getPreferences(username),
|
||||
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(),
|
||||
]);
|
||||
|
||||
return {
|
||||
...results[0],
|
||||
...results[1],
|
||||
thirdPartyAuthProviders: results[2],
|
||||
profileDataManager: results[3],
|
||||
timeZones: results[4],
|
||||
coaching: results[5],
|
||||
...account,
|
||||
...preferences,
|
||||
thirdPartyAuthProviders,
|
||||
profileDataManager,
|
||||
timeZones,
|
||||
coaching,
|
||||
shouldDisplayDemographicsSection: shouldDisplayDemographicsQuestionsResponse,
|
||||
...demographics,
|
||||
demographicsOptions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,9 +262,20 @@ export async function patchSettings(username, commitValues, userId) {
|
||||
// user/v1/preferences where it does update. This is the one we use.
|
||||
const preferenceKeys = ['time_zone'];
|
||||
const coachingKeys = ['coaching'];
|
||||
const accountCommitValues = omit(commitValues, preferenceKeys, coachingKeys);
|
||||
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 = [];
|
||||
|
||||
if (!isEmpty(accountCommitValues)) {
|
||||
@@ -197,6 +287,12 @@ export async function patchSettings(username, commitValues, userId) {
|
||||
if (!isEmpty(coachingCommitValues)) {
|
||||
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
|
||||
}
|
||||
if (!isEmpty(demographicsCommitValues)) {
|
||||
patchRequests.push(patchDemographics(userId, demographicsCommitValues));
|
||||
}
|
||||
if (!isEmpty(certCommitValues)) {
|
||||
patchRequests.push(postVerifiedNameConfig(username, certCommitValues));
|
||||
}
|
||||
|
||||
const results = await Promise.all(patchRequests);
|
||||
// Assigns in order of requests. Preference keys
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
export function modifyObjectKeys(object, modify) {
|
||||
// If the passed in object is not an object, return it.
|
||||
if (
|
||||
object === undefined ||
|
||||
object === null ||
|
||||
(typeof object !== 'object' && !Array.isArray(object))
|
||||
) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
return object.map(value => modifyObjectKeys(value, modify));
|
||||
}
|
||||
|
||||
// Otherwise, process all its keys.
|
||||
const result = {};
|
||||
Object.entries(object).forEach(([key, value]) => {
|
||||
result[modify(key)] = modifyObjectKeys(value, modify);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function camelCaseObject(object) {
|
||||
return modifyObjectKeys(object, camelCase);
|
||||
}
|
||||
|
||||
export function snakeCaseObject(object) {
|
||||
return modifyObjectKeys(object, snakeCase);
|
||||
}
|
||||
|
||||
export function convertKeyNames(object, nameMap) {
|
||||
const transformer = key => (nameMap[key] === undefined ? key : nameMap[key]);
|
||||
|
||||
return modifyObjectKeys(object, transformer);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
import {
|
||||
modifyObjectKeys,
|
||||
camelCaseObject,
|
||||
snakeCaseObject,
|
||||
convertKeyNames,
|
||||
} from './dataUtils';
|
||||
|
||||
describe('modifyObjectKeys', () => {
|
||||
it('should use the provided modify function to change all keys in and object and its children', () => {
|
||||
function meowKeys(key) {
|
||||
return `${key}Meow`;
|
||||
}
|
||||
|
||||
const result = modifyObjectKeys(
|
||||
{
|
||||
one: undefined,
|
||||
two: null,
|
||||
three: '',
|
||||
four: 0,
|
||||
five: NaN,
|
||||
six: [1, 2, { seven: 'woof' }],
|
||||
eight: { nine: { ten: 'bark' }, eleven: true },
|
||||
},
|
||||
meowKeys,
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
oneMeow: undefined,
|
||||
twoMeow: null,
|
||||
threeMeow: '',
|
||||
fourMeow: 0,
|
||||
fiveMeow: NaN,
|
||||
sixMeow: [1, 2, { sevenMeow: 'woof' }],
|
||||
eightMeow: { nineMeow: { tenMeow: 'bark' }, elevenMeow: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('camelCaseObject', () => {
|
||||
it('should make everything camelCase', () => {
|
||||
const result = camelCaseObject({
|
||||
what_now: 'brown cow',
|
||||
but_who: { says_you_people: 'okay then', but_how: { will_we_even_know: 'the song is over' } },
|
||||
'dot.dot.dot': 123,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
whatNow: 'brown cow',
|
||||
butWho: { saysYouPeople: 'okay then', butHow: { willWeEvenKnow: 'the song is over' } },
|
||||
dotDotDot: 123,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('snakeCaseObject', () => {
|
||||
it('should make everything snake_case', () => {
|
||||
const result = snakeCaseObject({
|
||||
whatNow: 'brown cow',
|
||||
butWho: { saysYouPeople: 'okay then', butHow: { willWeEvenKnow: 'the song is over' } },
|
||||
'dot.dot.dot': 123,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
what_now: 'brown cow',
|
||||
but_who: { says_you_people: 'okay then', but_how: { will_we_even_know: 'the song is over' } },
|
||||
dot_dot_dot: 123,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertKeyNames', () => {
|
||||
it('should replace the specified keynames', () => {
|
||||
const result = convertKeyNames(
|
||||
{
|
||||
one: { two: { three: 'four' } },
|
||||
five: 'six',
|
||||
},
|
||||
{
|
||||
two: 'blue',
|
||||
five: 'alive',
|
||||
seven: 'heaven',
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
one: { blue: { three: 'four' } },
|
||||
alive: 'six',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,3 @@
|
||||
export {
|
||||
camelCaseObject,
|
||||
convertKeyNames,
|
||||
modifyObjectKeys,
|
||||
snakeCaseObject,
|
||||
} from './dataUtils';
|
||||
export {
|
||||
AsyncActionType,
|
||||
getModuleState,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
// Messages
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import messages from './messages';
|
||||
|
||||
// Components
|
||||
@@ -22,13 +23,14 @@ const BeforeProceedingBanner = (props) => {
|
||||
<FormattedMessage
|
||||
id="account.settings.delete.account.before.proceeding"
|
||||
defaultMessage="Before proceeding, please {actionLink}."
|
||||
description="Error that appears if you are trying to delete your edX account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
||||
description="Error that appears if you are trying to delete your account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
||||
values={{
|
||||
actionLink: (
|
||||
<Hyperlink destination={supportArticleUrl}>
|
||||
{intl.formatMessage(messages[instructionMessageId])}
|
||||
</Hyperlink>
|
||||
),
|
||||
siteName: getConfig().SITE_NAME,
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, Input, Modal, ValidationFormGroup } from '@edx/paragon';
|
||||
import {
|
||||
Button, Input, Modal, ValidationFormGroup,
|
||||
} from '@edx/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';
|
||||
import PrintingInstructions from './PrintingInstructions';
|
||||
@@ -19,6 +22,8 @@ export class ConfirmationModal extends Component {
|
||||
switch (reason) {
|
||||
case 'empty-password':
|
||||
return 'account.settings.delete.account.error.no.password';
|
||||
case 'invalid-password':
|
||||
return 'account.settings.delete.account.error.invalid.password';
|
||||
default:
|
||||
return 'account.settings.delete.account.error.unable.to.delete';
|
||||
}
|
||||
@@ -31,10 +36,9 @@ export class ConfirmationModal extends Component {
|
||||
return null;
|
||||
}
|
||||
const headerMessageId = this.getShortErrorMessageId(errorType);
|
||||
const detailsMessageId =
|
||||
reason === 'empty-password'
|
||||
? null
|
||||
: 'account.settings.delete.account.error.unable.to.delete.details';
|
||||
const detailsMessageId = reason === 'empty-password'
|
||||
? null
|
||||
: 'account.settings.delete.account.error.unable.to.delete.details';
|
||||
|
||||
return (
|
||||
<Alert
|
||||
@@ -62,11 +66,18 @@ export class ConfirmationModal extends Component {
|
||||
const open = ['confirming', 'pending', 'failed'].includes(status);
|
||||
const passwordFieldId = 'passwordFieldId';
|
||||
const invalidMessage = messages[this.getShortErrorMessageId(errorType)];
|
||||
|
||||
// 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.
|
||||
const deleteAccountModalText2MessageKey = getConfig().SITE_NAME === 'edX'
|
||||
? 'account.settings.delete.account.modal.text.2.edX'
|
||||
: 'account.settings.delete.account.modal.text.2';
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
||||
body={
|
||||
body={(
|
||||
<div>
|
||||
{this.renderError()}
|
||||
<Alert
|
||||
@@ -74,9 +85,17 @@ export class ConfirmationModal extends Component {
|
||||
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
|
||||
>
|
||||
<h6>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.text.1'])}
|
||||
{intl.formatMessage(
|
||||
messages['account.settings.delete.account.modal.text.1'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</h6>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.modal.text.2'])}</p>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
messages[deleteAccountModalText2MessageKey],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
@@ -98,9 +117,9 @@ export class ConfirmationModal extends Component {
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
buttons={[
|
||||
<Button className="btn-danger" onClick={onSubmit}>
|
||||
<Button variant="danger" onClick={onSubmit}>
|
||||
{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.delete'])}
|
||||
</Button>,
|
||||
]}
|
||||
|
||||
@@ -24,9 +24,13 @@ import ConnectedSuccessModal from './SuccessModal';
|
||||
import BeforeProceedingBanner from './BeforeProceedingBanner';
|
||||
|
||||
export class DeleteAccount extends React.Component {
|
||||
state = {
|
||||
password: '',
|
||||
};
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
password: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
if (this.state.password === '') {
|
||||
@@ -56,19 +60,42 @@ export class DeleteAccount extends React.Component {
|
||||
} = this.props;
|
||||
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
||||
|
||||
// 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.
|
||||
const deleteAccountText2MessageKey = getConfig().SITE_NAME === 'edX'
|
||||
? '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>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.1'])}</p>
|
||||
<p>{intl.formatMessage(messages['account.settings.delete.account.text.2'])}</p>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
messages['account.settings.delete.account.text.1'],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(
|
||||
messages[deleteAccountText2MessageKey],
|
||||
{ siteName: getConfig().SITE_NAME },
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
<PrintingInstructions />
|
||||
</p>
|
||||
<p className="text-danger h6">
|
||||
{intl.formatMessage(messages['account.settings.delete.account.text.warning'])}
|
||||
{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">
|
||||
@@ -77,7 +104,7 @@ export class DeleteAccount extends React.Component {
|
||||
</p>
|
||||
<p>
|
||||
<Button
|
||||
className="btn-outline-danger"
|
||||
variant="outline-danger"
|
||||
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
||||
disabled={!canDelete}
|
||||
>
|
||||
@@ -87,8 +114,8 @@ 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-"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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 () {
|
||||
return <></>;
|
||||
});
|
||||
jest.mock('./SuccessModal', () => function () {
|
||||
return <></>;
|
||||
});
|
||||
|
||||
import { DeleteAccount } from './DeleteAccount'; // eslint-disable-line import/first
|
||||
|
||||
@@ -37,6 +42,7 @@ describe('DeleteAccount', () => {
|
||||
</IntlProvider>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -2,22 +2,39 @@ import React from 'react';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import messages from './messages';
|
||||
|
||||
const PrintingInstructions = (props) => {
|
||||
const actionLink = (
|
||||
<Hyperlink
|
||||
destination="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
// TODO: What would a generic version of this link look like? Should
|
||||
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
|
||||
// We've removed the link from the default message.
|
||||
destination="https://support.edx.org/hc/en-us/sections/115004173027-Receive-and-Share-edX-Certificates"
|
||||
>
|
||||
{props.intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
|
||||
</Hyperlink>
|
||||
);
|
||||
|
||||
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
|
||||
// to allow edx.org to mention MicroMasters certificates to fulfill its business requirements.
|
||||
if (getConfig().SITE_NAME === 'edX') {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="account.settings.delete.account.text.3.edX"
|
||||
defaultMessage="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}."
|
||||
description="A message in the user account deletion area warning users that deleting their account will prevent them from accessing their certificates. 'actionLink' is a HTML link with a full sentence that describes how to print a certificate."
|
||||
values={{ actionLink }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="account.settings.delete.account.text.3"
|
||||
defaultMessage="You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion, {actionLink}."
|
||||
description="A message in the user account deletion area"
|
||||
defaultMessage="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."
|
||||
description="A message in the user account deletion area warning users that deleting their account will prevent them from accessing their certificates."
|
||||
values={{ actionLink }}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -11,13 +11,13 @@ export const SuccessModal = (props) => {
|
||||
<Modal
|
||||
open={status === 'deleted'}
|
||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.after.header'])}
|
||||
body={
|
||||
body={(
|
||||
<div>
|
||||
<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}
|
||||
|
||||
@@ -1,439 +1,513 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
/>,
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onMouseDown={[Function]}
|
||||
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-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>
|
||||
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"
|
||||
>
|
||||
<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 empty password confirmation modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id3"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
data-focus-guard={true}
|
||||
style={
|
||||
Object {
|
||||
"height": "0px",
|
||||
"left": "1px",
|
||||
"overflow": "hidden",
|
||||
"padding": 0,
|
||||
"position": "fixed",
|
||||
"top": "1px",
|
||||
"width": "1px",
|
||||
}
|
||||
}
|
||||
tabIndex={0}
|
||||
/>
|
||||
<div
|
||||
data-focus-lock-disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id3"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-danger mt-n2"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-exclamation-circle fa-w-16 mr-2"
|
||||
data-icon="exclamation-circle"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h6>
|
||||
A password is required
|
||||
</h6>
|
||||
<p
|
||||
className="text-danger"
|
||||
>
|
||||
Sorry, there was an error trying to process your request. Please try again later.
|
||||
</p>
|
||||
</div>
|
||||
</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>
|
||||
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"
|
||||
>
|
||||
<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="passwordFieldId-invalid-feedback"
|
||||
className="form-control is-invalid"
|
||||
id="passwordFieldId"
|
||||
name="password"
|
||||
onChange={[MockFunction]}
|
||||
type="password"
|
||||
value="fluffy bunnies"
|
||||
/>
|
||||
<strong
|
||||
className="invalid-feedback"
|
||||
id="passwordFieldId-invalid-feedback"
|
||||
>
|
||||
A password is required
|
||||
</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={0}
|
||||
/>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id2"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
>
|
||||
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. edX 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 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.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</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-danger"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Yes, Delete
|
||||
</button>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click show d-block"
|
||||
onMouseDown={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id6"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id6"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-danger mt-n2"
|
||||
>
|
||||
<div>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-exclamation-circle fa-w-16 mr-2"
|
||||
data-icon="exclamation-circle"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h6>
|
||||
A password is required
|
||||
</h6>
|
||||
<p
|
||||
className="text-danger"
|
||||
>
|
||||
Sorry, there was an error trying to process your request. Please try again later.
|
||||
</p>
|
||||
</div>
|
||||
</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. edX 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 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.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</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="passwordFieldId-invalid-feedback"
|
||||
className="form-control is-invalid"
|
||||
id="passwordFieldId"
|
||||
name="password"
|
||||
onChange={[MockFunction]}
|
||||
type="password"
|
||||
value="fluffy bunnies"
|
||||
/>
|
||||
<strong
|
||||
className="invalid-feedback"
|
||||
id="passwordFieldId-invalid-feedback"
|
||||
>
|
||||
A password is required
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Yes, Delete
|
||||
</button>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click show d-block"
|
||||
onMouseDown={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id4"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
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-content"
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
className="modal-content"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id4"
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
Are you sure?
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert-warning mt-n2"
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
>
|
||||
<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"
|
||||
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 {}}
|
||||
/>
|
||||
</svg>
|
||||
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>
|
||||
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>
|
||||
<h6>
|
||||
You have selected "Delete My Account". Deletion of your account and personal data is permanent and cannot be undone. edX 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 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.
|
||||
</p>
|
||||
<p>
|
||||
<span>
|
||||
You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</p>
|
||||
<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
|
||||
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-danger"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
<div
|
||||
className="modal-footer"
|
||||
>
|
||||
Yes, Delete
|
||||
</button>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -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>
|
||||
@@ -11,31 +11,22 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
We're sorry to see you go!
|
||||
</p>
|
||||
<p>
|
||||
Please note: Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.
|
||||
Please note: 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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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 like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</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"
|
||||
>
|
||||
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 edX.
|
||||
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 localhost.
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
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"
|
||||
@@ -47,9 +38,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
||||
<button
|
||||
className="btn btn-outline-danger"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
Delete My Account
|
||||
@@ -61,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>
|
||||
@@ -69,31 +58,22 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
We're sorry to see you go!
|
||||
</p>
|
||||
<p>
|
||||
Please note: Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.
|
||||
Please note: 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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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 like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</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"
|
||||
>
|
||||
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 edX.
|
||||
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 localhost.
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
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"
|
||||
@@ -105,9 +85,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
<button
|
||||
className="btn btn-outline-danger"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onClick={null}
|
||||
type="button"
|
||||
>
|
||||
Delete My Account
|
||||
@@ -136,17 +114,16 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
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>
|
||||
@@ -155,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>
|
||||
@@ -163,31 +140,22 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
We're sorry to see you go!
|
||||
</p>
|
||||
<p>
|
||||
Please note: Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.
|
||||
Please note: 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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
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 like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion,
|
||||
<a
|
||||
href="https://edx.readthedocs.io/projects/edx-guide-for-students/en/latest/SFD_certificates.html#printing-a-certificate"
|
||||
onClick={[Function]}
|
||||
target="_self"
|
||||
>
|
||||
follow the instructions for printing or downloading a certificate
|
||||
</a>
|
||||
.
|
||||
</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"
|
||||
>
|
||||
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 edX.
|
||||
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 localhost.
|
||||
</p>
|
||||
<p>
|
||||
<a
|
||||
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"
|
||||
@@ -199,9 +167,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
<button
|
||||
className="btn btn-outline-danger"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onClick={null}
|
||||
type="button"
|
||||
>
|
||||
Delete My Account
|
||||
@@ -230,17 +196,16 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Before proceeding, please
|
||||
<a
|
||||
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,14 +1,110 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
/>,
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onMouseDown={[Function]}
|
||||
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-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 2`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
@@ -16,61 +112,193 @@ exports[`SuccessModal should match default closed success modal snapshot 1`] = `
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
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-header"
|
||||
className="modal-content"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id2"
|
||||
>
|
||||
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>
|
||||
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
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 2`] = `
|
||||
<div>
|
||||
exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
/>,
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onMouseDown={[Function]}
|
||||
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-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 4`] = `
|
||||
Array [
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>,
|
||||
<div
|
||||
className="modal fade"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
@@ -78,234 +306,181 @@ exports[`SuccessModal should match default closed success modal snapshot 2`] = `
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<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 js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
||||
<div>
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onMouseDown={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id6"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
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-content"
|
||||
data-focus-lock-disabled="disabled"
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onScrollCapture={[Function]}
|
||||
onTouchMoveCapture={[Function]}
|
||||
onWheelCapture={[Function]}
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
className="modal-content"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id6"
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id4"
|
||||
>
|
||||
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>
|
||||
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
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `
|
||||
<div>
|
||||
<div
|
||||
className="fade"
|
||||
role="presentation"
|
||||
/>
|
||||
<div
|
||||
className="modal js-close-modal-on-click fade"
|
||||
onMouseDown={[Function]}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id8"
|
||||
aria-modal={true}
|
||||
className=""
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
>
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id8"
|
||||
>
|
||||
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 js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton7"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||
<div>
|
||||
Array [
|
||||
<div
|
||||
className="modal-backdrop show"
|
||||
role="presentation"
|
||||
/>
|
||||
/>,
|
||||
<div
|
||||
className="modal js-close-modal-on-click show d-block"
|
||||
onMouseDown={[Function]}
|
||||
className="modal show d-block"
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
aria-labelledby="id10"
|
||||
aria-labelledby="id5"
|
||||
aria-modal={true}
|
||||
className="modal-dialog"
|
||||
role="dialog"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="modal-content"
|
||||
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-header"
|
||||
className="modal-content"
|
||||
>
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id10"
|
||||
<div
|
||||
className="modal-header"
|
||||
>
|
||||
We're sorry to see you go! Your account will be deleted shortly.
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
className="modal-body"
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="h6"
|
||||
<h2
|
||||
className="modal-title"
|
||||
id="id5"
|
||||
>
|
||||
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>
|
||||
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
|
||||
className="modal-footer"
|
||||
>
|
||||
<button
|
||||
className="btn js-close-modal-on-click btn-secondary"
|
||||
id="paragonCloseModalButton9"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -15,7 +15,9 @@ export function* handleDeleteAccount(action) {
|
||||
const response = yield call(postDeleteAccount, action.payload.password);
|
||||
yield put(deleteAccountSuccess(response));
|
||||
} catch (e) {
|
||||
if (typeof e.response.data === 'string') {
|
||||
if (e.response.status === 403) {
|
||||
yield put(deleteAccountFailure('invalid-password'));
|
||||
} else if (typeof e.response.data === 'string') {
|
||||
yield put(deleteAccountFailure());
|
||||
} else {
|
||||
throw e;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -13,22 +13,27 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.delete.account.text.1': {
|
||||
id: 'account.settings.delete.account.text.1',
|
||||
defaultMessage: 'Please note: Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.',
|
||||
defaultMessage: '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.',
|
||||
description: 'A message in the user account deletion area',
|
||||
},
|
||||
'account.settings.delete.account.text.2': {
|
||||
id: 'account.settings.delete.account.text.2',
|
||||
defaultMessage: 'Once your account is deleted, you cannot use it to take courses on {siteName}.',
|
||||
description: 'A message in the user account deletion area',
|
||||
},
|
||||
'account.settings.delete.account.text.2.edX': {
|
||||
id: 'account.settings.delete.account.text.2.edX',
|
||||
defaultMessage: '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.',
|
||||
description: 'A message in the user account deletion area',
|
||||
},
|
||||
'account.settings.delete.account.text.3.link': {
|
||||
id: 'account.settings.delete.account.text.3.link',
|
||||
defaultMessage: 'follow the instructions for printing or downloading a certificate',
|
||||
description: 'This text will be a link to a technical support page; it will go in the phrase If you want to make a copy of these for your records, ______ .',
|
||||
defaultMessage: 'Follow these instructions for printing or downloading a certificate',
|
||||
description: 'This text is a link to a technical support page where users can learn how to print or download their certificates.',
|
||||
},
|
||||
'account.settings.delete.account.text.warning': {
|
||||
id: 'account.settings.delete.account.text.warning',
|
||||
defaultMessage: '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 edX.',
|
||||
defaultMessage: '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}.',
|
||||
description: 'A message in the user account deletion area',
|
||||
},
|
||||
'account.settings.delete.account.text.change.instead': {
|
||||
@@ -39,13 +44,18 @@ const messages = defineMessages({
|
||||
'account.settings.delete.account.button': {
|
||||
id: 'account.settings.delete.account.button',
|
||||
defaultMessage: 'Delete My Account',
|
||||
description: 'Button label to permanently delete your edX account',
|
||||
description: 'Button label to permanently delete your platform account',
|
||||
},
|
||||
'account.settings.delete.account.please.activate': {
|
||||
id: 'account.settings.delete.account.please.activate',
|
||||
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',
|
||||
@@ -58,11 +68,16 @@ const messages = defineMessages({
|
||||
},
|
||||
'account.settings.delete.account.modal.text.1': {
|
||||
id: 'account.settings.delete.account.modal.text.1',
|
||||
defaultMessage: 'You have selected "Delete My Account". Deletion of your account and personal data is permanent and cannot be undone. edX will not be able to recover your account or the data that is deleted.',
|
||||
defaultMessage: '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.',
|
||||
description: 'Messaging in the dialog asking user to confirm that they want to delete their entire account',
|
||||
},
|
||||
'account.settings.delete.account.modal.text.2': {
|
||||
id: 'account.settings.delete.account.modal.text.2',
|
||||
defaultMessage: 'If you proceed, you will be unable to use this account to take courses on {siteName}.',
|
||||
description: 'Messaging in the dialog asking user to confirm that they want to delete their entire account',
|
||||
},
|
||||
'account.settings.delete.account.modal.text.2.edX': {
|
||||
id: 'account.settings.delete.account.modal.text.2.edX',
|
||||
defaultMessage: '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.',
|
||||
description: 'Messaging in the dialog asking user to confirm that they want to delete their entire account',
|
||||
},
|
||||
@@ -91,6 +106,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'A password is required',
|
||||
description: 'Error message when user has not entered their password',
|
||||
},
|
||||
'account.settings.delete.account.error.invalid.password': {
|
||||
id: 'account.settings.delete.account.error.invalid.password',
|
||||
defaultMessage: 'Password is incorrect',
|
||||
description: 'Error message when user has entered incorrect password',
|
||||
},
|
||||
'account.settings.delete.account.error.unable.to.delete.details': {
|
||||
id: 'account.settings.delete.account.error.unable.to.delete.details',
|
||||
defaultMessage: 'Sorry, there was an error trying to process your request. Please try again later.',
|
||||
|
||||
79
src/account-settings/demographics/Checkboxes.jsx
Normal file
79
src/account-settings/demographics/Checkboxes.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CheckBox } from '@edx/paragon';
|
||||
import { DECLINED } from '../data/constants';
|
||||
|
||||
const Checkboxes = (props) => {
|
||||
const {
|
||||
id,
|
||||
options,
|
||||
values,
|
||||
onChange,
|
||||
} = props;
|
||||
|
||||
const [selected, setSelected] = useState(values);
|
||||
useEffect(() => {
|
||||
onChange(id, selected);
|
||||
}, [id, onChange, selected]);
|
||||
|
||||
const handleToggle = (value, option) => {
|
||||
// If the user checked 'declined', uncheck all other options
|
||||
if (value && option === DECLINED) {
|
||||
setSelected([DECLINED]);
|
||||
return;
|
||||
}
|
||||
|
||||
// If option checked, make sure this option is in `selected` (and remove 'declined')
|
||||
if (value && !selected.includes(option)) {
|
||||
const newSelected = selected.filter(i => i !== DECLINED).concat(option);
|
||||
setSelected(newSelected);
|
||||
}
|
||||
|
||||
// If unchecked, make sure this option is NOT in `selected`
|
||||
if (!value) {
|
||||
setSelected(selected.filter(i => i !== option));
|
||||
}
|
||||
};
|
||||
|
||||
const renderCheckboxes = () => options.map((option, index) => {
|
||||
const isFirst = index === 0;
|
||||
const isChecked = selected.includes(option.value);
|
||||
return (
|
||||
<div key={option.value} className="checkboxOption">
|
||||
<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)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div role="group">
|
||||
{renderCheckboxes()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Checkboxes.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
options: PropTypes.arrayOf(PropTypes.shape({
|
||||
value: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
})),
|
||||
values: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Checkboxes.defaultProps = {
|
||||
options: [],
|
||||
values: [],
|
||||
};
|
||||
|
||||
export default Checkboxes;
|
||||
357
src/account-settings/demographics/DemographicsSection.jsx
Normal file
357
src/account-settings/demographics/DemographicsSection.jsx
Normal file
@@ -0,0 +1,357 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import {
|
||||
FormattedMessage,
|
||||
injectIntl,
|
||||
intlShape,
|
||||
} from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Hyperlink, Input } from '@edx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash.get';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import memoize from 'memoize-one';
|
||||
import { demographicsSectionSelector } from '../data/selectors';
|
||||
import EditableField from '../EditableField';
|
||||
import Checkboxes from './Checkboxes';
|
||||
import Alert from '../Alert';
|
||||
import { saveMultipleSettings, updateDraft } from '../data/actions';
|
||||
import {
|
||||
OTHER,
|
||||
SELF_DESCRIBE,
|
||||
} from '../data/constants';
|
||||
import messages from './DemographicsSection.messages';
|
||||
|
||||
class DemographicsSection extends React.Component {
|
||||
// We check the `demographicsOptions` prop to see if it is empty before we attempt to extract and
|
||||
// format the available options for each question from the API response.
|
||||
getApiOptions = memoize((demographicsOptions) => (this.hasRetrievedDemographicsOptions() && {
|
||||
demographicsGenderOptions: this.addDefaultOption('account.settings.field.demographics.gender.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.gender.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
/* Ethnicity options don't need the blank/default option */
|
||||
demographicsEthnicityOptions: demographicsOptions.actions.POST.user_ethnicity.child.children.ethnicity.choices.map(
|
||||
key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}),
|
||||
),
|
||||
demographicsIncomeOptions: this.addDefaultOption('account.settings.field.demographics.income.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.income.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
demographicsMilitaryHistoryOptions: this.addDefaultOption('account.settings.field.demographics.military_history.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.military_history.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
demographicsEducationLevelOptions: this.addDefaultOption('account.settings.field.demographics.education_level.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.learner_education_level.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
demographicsWorkStatusOptions: this.addDefaultOption('account.settings.field.demographics.work_status.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.work_status.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
demographicsWorkSectorOptions: this.addDefaultOption('account.settings.field.demographics.work_sector.options.empty')
|
||||
.concat(demographicsOptions.actions.POST.current_work_sector.choices.map(key => ({
|
||||
value: key.value,
|
||||
label: key.display_name,
|
||||
}))),
|
||||
}));
|
||||
|
||||
ethnicityFieldDisplay = (demographicsEthnicityOptions) => {
|
||||
let ethnicities = [];
|
||||
if (get(this, 'props.formValues.demographics_user_ethnicity')) {
|
||||
ethnicities = this.props.formValues.demographics_user_ethnicity;
|
||||
}
|
||||
return ethnicities.map((e) => {
|
||||
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
|
||||
return matchingOption && matchingOption.label;
|
||||
}).join(', ');
|
||||
};
|
||||
|
||||
handleEditableFieldChange = (name, value) => {
|
||||
this.props.updateDraft(name, value);
|
||||
};
|
||||
|
||||
handleSubmit = (formId) => {
|
||||
// We have some custom fields in this section. Instead of relying on the
|
||||
// submitted values, submit the values stored in 'drafts'.
|
||||
const { drafts } = this.props;
|
||||
const settingsArray = Object.entries(drafts).map(([field, value]) => ({
|
||||
formId: field,
|
||||
commitValues: value,
|
||||
}));
|
||||
|
||||
this.props.saveMultipleSettings(settingsArray, formId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility method that adds the specified message as a default option to the list of available
|
||||
* choices.
|
||||
*
|
||||
* @param {*} messageId id of message matching desired default label text
|
||||
*/
|
||||
addDefaultOption(messageId) {
|
||||
return [{
|
||||
value: '',
|
||||
label: this.props.intl.formatMessage(messages[messageId]),
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that helps determine if we were able to retrieve the available options for
|
||||
* the Demographics questions. Returns true if the `demographicsOptions` prop is _not_ empty,
|
||||
* otherwise false. This prop being empty is indicative of a failure communicating with the
|
||||
* Demographics IDA's API.
|
||||
*/
|
||||
hasRetrievedDemographicsOptions() {
|
||||
return !isEmpty(this.props.formValues.demographicsOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* If an error is encountered when trying to communicate with the Demographics IDA then we will
|
||||
* display an Alert letting the user know that their info will not be displayed and temporarily
|
||||
* cannot be updated.
|
||||
*/
|
||||
renderDemographicsServiceIssueWarning() {
|
||||
if (!isEmpty(this.props.formErrors.demographicsError)
|
||||
|| !this.hasRetrievedDemographicsOptions()) {
|
||||
return (
|
||||
<div
|
||||
tabIndex="-1"
|
||||
ref={this.alertRef}
|
||||
>
|
||||
<Alert className="alert alert-danger" role="alert">
|
||||
<FormattedMessage
|
||||
id="account.settings.message.demographics.service.issue"
|
||||
defaultMessage="An error occurred attempting to retrieve or save your account information. Please try again later."
|
||||
description="alert message informing the user that the there is a problem retrieving or updating information from the Demographics microservice"
|
||||
/>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const editableFieldProps = {
|
||||
onChange: this.handleEditableFieldChange,
|
||||
onSubmit: this.handleSubmit,
|
||||
};
|
||||
|
||||
const {
|
||||
demographicsGenderOptions,
|
||||
demographicsEthnicityOptions,
|
||||
demographicsIncomeOptions,
|
||||
demographicsMilitaryHistoryOptions,
|
||||
demographicsEducationLevelOptions,
|
||||
demographicsWorkStatusOptions,
|
||||
demographicsWorkSectorOptions,
|
||||
} = this.getApiOptions(this.props.formValues.demographicsOptions);
|
||||
|
||||
const showSelfDescribe = this.props.formValues.demographics_gender === SELF_DESCRIBE;
|
||||
const showWorkStatusDescribe = this.props.formValues.demographics_work_status === OTHER;
|
||||
|
||||
return (
|
||||
<div className="account-section pt-3 mb-5" id="demographics-information" ref={this.props.forwardRef}>
|
||||
<h2 className="section-heading h4 mb-3">
|
||||
{this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
||||
</h2>
|
||||
<p>
|
||||
<Hyperlink
|
||||
destination={`${getConfig().MARKETING_SITE_BASE_URL}/demographics`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{this.props.intl.formatMessage(
|
||||
messages['account.settings.section.demographics.why'],
|
||||
{
|
||||
siteName: getConfig().SITE_NAME,
|
||||
},
|
||||
)}
|
||||
</Hyperlink>
|
||||
</p>
|
||||
{this.renderDemographicsServiceIssueWarning()}
|
||||
{/*
|
||||
If the demographicsOptions props are empty then there is no need to display the fields as
|
||||
the user will not have any choices available to select, nor will they be able to update
|
||||
their answers.
|
||||
*/}
|
||||
{this.hasRetrievedDemographicsOptions() && (
|
||||
<div id="demographics-fields">
|
||||
<EditableField
|
||||
name="demographics_gender"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_gender}
|
||||
userSuppliedValue={showSelfDescribe ? this.props.formValues.demographics_gender_description : null}
|
||||
options={demographicsGenderOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender.empty'])}
|
||||
{...editableFieldProps}
|
||||
>
|
||||
{showSelfDescribe && (
|
||||
<Input
|
||||
name="demographics_gender_description"
|
||||
id="field-demographics_gender_description"
|
||||
type="text"
|
||||
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description.empty'])}
|
||||
value={this.props.formValues.demographics_gender_description}
|
||||
onChange={(e) => this.handleEditableFieldChange('demographics_gender_description', e.target.value)}
|
||||
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description'])}
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
</EditableField>
|
||||
<EditableField
|
||||
name="demographics_user_ethnicity"
|
||||
type="select"
|
||||
hidden
|
||||
value={this.ethnicityFieldDisplay(demographicsEthnicityOptions)}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity.empty'])}
|
||||
{...editableFieldProps}
|
||||
>
|
||||
<Checkboxes
|
||||
id="demographics_user_ethnicity"
|
||||
options={demographicsEthnicityOptions}
|
||||
values={this.props.formValues.demographics_user_ethnicity}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
</EditableField>
|
||||
<EditableField
|
||||
name="demographics_income"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_income}
|
||||
options={demographicsIncomeOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.income'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.income.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="demographics_military_history"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_military_history}
|
||||
options={demographicsMilitaryHistoryOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="demographics_learner_education_level"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_learner_education_level}
|
||||
options={demographicsEducationLevelOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="demographics_parent_education_level"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_parent_education_level}
|
||||
options={demographicsEducationLevelOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="demographics_work_status"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_work_status}
|
||||
userSuppliedValue={showWorkStatusDescribe
|
||||
? this.props.formValues.demographics_work_status_description
|
||||
: null}
|
||||
options={demographicsWorkStatusOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status.empty'])}
|
||||
{...editableFieldProps}
|
||||
>
|
||||
{showWorkStatusDescribe && (
|
||||
<Input
|
||||
name="demographics_work_status_description"
|
||||
id="field-demographics_work_status_description"
|
||||
type="text"
|
||||
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description.empty'])}
|
||||
value={this.props.formValues.demographics_work_status_description}
|
||||
onChange={(e) => this.handleEditableFieldChange('demographics_work_status_description', e.target.value)}
|
||||
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description'])}
|
||||
className="mt-1"
|
||||
/>
|
||||
)}
|
||||
</EditableField>
|
||||
<EditableField
|
||||
name="demographics_current_work_sector"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_current_work_sector}
|
||||
options={demographicsWorkSectorOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<EditableField
|
||||
name="demographics_future_work_sector"
|
||||
type="select"
|
||||
value={this.props.formValues.demographics_future_work_sector}
|
||||
options={demographicsWorkSectorOptions}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DemographicsSection.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
formValues: PropTypes.shape({
|
||||
demographics_gender: PropTypes.string,
|
||||
demographics_user_ethnicity: PropTypes.shape([]),
|
||||
demographics_income: PropTypes.string,
|
||||
demographics_military_history: PropTypes.string,
|
||||
demographics_learner_education_level: PropTypes.string,
|
||||
demographics_parent_education_level: PropTypes.string,
|
||||
demographics_work_status: PropTypes.string,
|
||||
demographics_current_work_sector: PropTypes.string,
|
||||
demographics_future_work_sector: PropTypes.string,
|
||||
demographics_work_status_description: PropTypes.string,
|
||||
demographics_gender_description: PropTypes.string,
|
||||
demographicsOptions: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
drafts: PropTypes.shape({
|
||||
demographics_gender: PropTypes.string,
|
||||
demographics_user_ethnicity: PropTypes.shape([]),
|
||||
demographics_income: PropTypes.string,
|
||||
demographics_military_history: PropTypes.string,
|
||||
demographics_learner_education_level: PropTypes.string,
|
||||
demographics_parent_education_level: PropTypes.string,
|
||||
demographics_work_status: PropTypes.string,
|
||||
demographics_current_work_sector: PropTypes.string,
|
||||
demographics_future_work_sector: PropTypes.string,
|
||||
demographics_work_status_description: PropTypes.string,
|
||||
demographics_gender_description: PropTypes.string,
|
||||
demographicsOptions: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
formErrors: PropTypes.shape({
|
||||
demographicsError: PropTypes.string,
|
||||
}).isRequired,
|
||||
forwardRef: PropTypes.func.isRequired,
|
||||
updateDraft: PropTypes.func.isRequired,
|
||||
saveMultipleSettings: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default connect(demographicsSectionSelector, {
|
||||
saveMultipleSettings,
|
||||
updateDraft,
|
||||
})(injectIntl(DemographicsSection));
|
||||
@@ -0,0 +1,170 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
/* Demographics section heading */
|
||||
'account.settings.section.demographics.information': {
|
||||
id: 'account.settings.section.demographics.information',
|
||||
defaultMessage: 'Optional Information',
|
||||
description: 'The optional information section heading.',
|
||||
},
|
||||
/* Gender identity */
|
||||
'account.settings.field.demographics.gender': {
|
||||
id: 'account.settings.field.demographics.gender',
|
||||
defaultMessage: 'Gender identity',
|
||||
description: 'Label for account settings gender identity field.',
|
||||
},
|
||||
'account.settings.field.demographics.gender.empty': {
|
||||
id: 'account.settings.field.demographics.gender.empty',
|
||||
defaultMessage: 'Add gender identity',
|
||||
description: 'Placeholder for empty account settings gender identity field.',
|
||||
},
|
||||
'account.settings.field.demographics.gender.options.empty': {
|
||||
id: 'account.settings.field.demographics.gender.options.empty',
|
||||
defaultMessage: 'Select a gender identity',
|
||||
description: 'Placeholder for the gender identity options dropdown.',
|
||||
},
|
||||
'account.settings.field.demographics.gender_description': {
|
||||
id: 'account.settings.field.demographics.gender_description',
|
||||
defaultMessage: 'Gender identity description',
|
||||
description: 'Label for account settings gender identity description field.',
|
||||
},
|
||||
'account.settings.field.demographics.gender_description.empty': {
|
||||
id: 'account.settings.field.demographics.gender_description.empty',
|
||||
defaultMessage: 'Enter description',
|
||||
description: 'Placeholder for empty account settings gender identity field.',
|
||||
},
|
||||
/* Ethnicity */
|
||||
'account.settings.field.demographics.ethnicity': {
|
||||
id: 'account.settings.field.demographics.ethnicity',
|
||||
defaultMessage: 'Race/Ethnicity identity',
|
||||
description: 'Label for account settings ethnic background field.',
|
||||
},
|
||||
'account.settings.field.demographics.ethnicity.empty': {
|
||||
id: 'account.settings.field.demographics.ethnicity.empty',
|
||||
defaultMessage: 'Add race/ethnicity identity',
|
||||
description: 'Placeholder for empty account settings ethnic background field.',
|
||||
},
|
||||
'account.settings.field.demographics.ethnicity.options.empty': {
|
||||
id: 'account.settings.field.demographics.ethnicity.options.empty',
|
||||
defaultMessage: 'Select all that apply', // TODO: Is this the desired text?
|
||||
description: 'Placeholder for the ethnic background options field.',
|
||||
},
|
||||
/* Income */
|
||||
'account.settings.field.demographics.income': {
|
||||
id: 'account.settings.field.demographics.income',
|
||||
defaultMessage: 'Family income',
|
||||
description: 'Label for account settings household income field.',
|
||||
},
|
||||
'account.settings.field.demographics.income.empty': {
|
||||
id: 'account.settings.field.demographics.income.empty',
|
||||
defaultMessage: 'Add family income',
|
||||
description: 'Placeholder for empty account settings household income field.',
|
||||
},
|
||||
'account.settings.field.demographics.income.options.empty': {
|
||||
id: 'account.settings.field.demographics.income.options.empty',
|
||||
defaultMessage: 'Select a family income range',
|
||||
description: 'Placeholder for the household income dropdown.',
|
||||
},
|
||||
/* Military history */
|
||||
'account.settings.field.demographics.military_history': {
|
||||
id: 'account.settings.field.demographics.military_history',
|
||||
defaultMessage: 'U.S. Military status',
|
||||
description: 'Label for account settings military history field.',
|
||||
},
|
||||
'account.settings.field.demographics.military_history.empty': {
|
||||
id: 'account.settings.field.demographics.military_history.empty',
|
||||
defaultMessage: 'Add military status',
|
||||
description: 'Placeholder for empty account settings military history field.',
|
||||
},
|
||||
'account.settings.field.demographics.military_history.options.empty': {
|
||||
id: 'account.settings.field.demographics.military_history.options.empty',
|
||||
defaultMessage: 'Select military status',
|
||||
description: 'Placeholder for the military history dropdown.',
|
||||
},
|
||||
/* Learner and family education level */
|
||||
'account.settings.field.demographics.learner_education_level': {
|
||||
id: 'account.settings.field.demographics.learner_education_level',
|
||||
defaultMessage: 'Your education level',
|
||||
description: 'Label for account settings learner education level field.',
|
||||
},
|
||||
'account.settings.field.demographics.learner_education_level.empty': {
|
||||
id: 'account.settings.field.demographics.learner_education_level.empty',
|
||||
defaultMessage: 'Add education level',
|
||||
description: 'Placeholder for empty account settings learner education level field.',
|
||||
},
|
||||
'account.settings.field.demographics.parent_education_level': {
|
||||
id: 'account.settings.field.demographics.parent_education_level',
|
||||
defaultMessage: 'Parents/Guardians education level',
|
||||
description: 'Label for account settings parent education level field.',
|
||||
},
|
||||
'account.settings.field.demographics.parent_education_level.empty': {
|
||||
id: 'account.settings.field.demographics.parent_education_level.empty',
|
||||
defaultMessage: 'Add education level',
|
||||
description: 'Placeholder for empty account settings parent education level field.',
|
||||
},
|
||||
'account.settings.field.demographics.education_level.options.empty': {
|
||||
id: 'account.settings.field.demographics.education_level.options.empty',
|
||||
defaultMessage: 'Select education level',
|
||||
description: 'Placeholder for the education level options dropdown.',
|
||||
},
|
||||
/* Work status */
|
||||
'account.settings.field.demographics.work_status': {
|
||||
id: 'account.settings.field.demographics.work_status',
|
||||
defaultMessage: 'Employment status',
|
||||
description: 'Label for account settings work status field.',
|
||||
},
|
||||
'account.settings.field.demographics.work_status.empty': {
|
||||
id: 'account.settings.field.demographics.work_status.empty',
|
||||
defaultMessage: 'Add employment status',
|
||||
description: 'Placeholder for empty account settings work status field.',
|
||||
},
|
||||
'account.settings.field.demographics.work_status.options.empty': {
|
||||
id: 'account.settings.field.demographics.work_status.options.empty',
|
||||
defaultMessage: 'Select employment status',
|
||||
description: 'Placeholder for the work status options dropdown.',
|
||||
},
|
||||
'account.settings.field.demographics.work_status_description': {
|
||||
id: 'account.settings.field.demographics.work_status_description',
|
||||
defaultMessage: 'Employment status description',
|
||||
description: 'Label for account settings work status description field.',
|
||||
},
|
||||
'account.settings.field.demographics.work_status_description.empty': {
|
||||
id: 'account.settings.field.demographics.work_status_description.empty',
|
||||
defaultMessage: 'Enter description',
|
||||
description: 'Placeholder for empty account settings work status description field.',
|
||||
},
|
||||
/* Work sector */
|
||||
'account.settings.field.demographics.current_work_sector': {
|
||||
id: 'account.settings.field.demographics.current_work_sector',
|
||||
defaultMessage: 'Current work industry',
|
||||
description: 'Label for account settings current work sector field.',
|
||||
},
|
||||
'account.settings.field.demographics.current_work_sector.empty': {
|
||||
id: 'account.settings.field.demographics.current_work_sector.empty',
|
||||
defaultMessage: 'Add work industry',
|
||||
description: 'Placeholder for empty account settings current work sector field.',
|
||||
},
|
||||
'account.settings.field.demographics.future_work_sector': {
|
||||
id: 'account.settings.field.demographics.future_work_sector',
|
||||
defaultMessage: 'Future work industry',
|
||||
description: 'Label for account settings future work sector field.',
|
||||
},
|
||||
'account.settings.field.demographics.future_work_sector.empty': {
|
||||
id: 'account.settings.field.demographics.future_work_sector.empty',
|
||||
defaultMessage: 'Add work industry',
|
||||
description: 'Placeholder for empty account settings future work sector field.',
|
||||
},
|
||||
'account.settings.field.demographics.work_sector.options.empty': {
|
||||
id: 'account.settings.field.demographics.work_sector.options.empty',
|
||||
defaultMessage: 'Select work industry',
|
||||
description: 'Placeholder for the work sector options dropdown.',
|
||||
},
|
||||
/* Legal copy link text */
|
||||
'account.settings.section.demographics.why': {
|
||||
id: 'account.settings.section.demographics.why',
|
||||
defaultMessage: 'Why does {siteName} collect this information?',
|
||||
description: 'Link text for a link to external legal text',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
140
src/account-settings/demographics/data/service.js
Normal file
140
src/account-settings/demographics/data/service.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import get from 'lodash.get';
|
||||
import { convertData, TO, FROM } from './utils';
|
||||
|
||||
/**
|
||||
* Utility method that attempts to extract errors from the response of a PATCH request in order to
|
||||
* display a warning or otherwise meaningful message to the user.
|
||||
*
|
||||
* @param {Error} error
|
||||
*/
|
||||
export function createDemographicsError(error) {
|
||||
const apiError = Object.create(error);
|
||||
// If the error received has the `httpResponseData` field in it, then we should have reason to
|
||||
// believe the Demographics service is alive and responding. Extract errors from fields where
|
||||
// appropriate so we can display them to the user.
|
||||
if (get(error, 'customAttributes.httpErrorResponseData')) {
|
||||
apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
||||
if (get(apiError, 'fieldErrors.gender_description')) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
apiError.fieldErrors.demographics_gender = apiError.fieldErrors.gender_description[0];
|
||||
delete apiError.fieldErrors.gender_description;
|
||||
} else if (get(apiError, 'fieldErrors.work_status_description')) {
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
apiError.fieldErrors.demographics_work_status = apiError.fieldErrors.work_status_description[0];
|
||||
delete apiError.fieldErrors.work_status_description;
|
||||
}
|
||||
// Otherwise, when the service is down, the error response will not contain a
|
||||
// `httpErrorResponseData` field. Add a generic 'demographicsError' field to the fieldErrors that
|
||||
// will trigger showing an Alert to the user to them them know the update was unsuccessful.
|
||||
} else {
|
||||
apiError.fieldErrors = {
|
||||
demographicsError: error.customAttributes.httpErrorType,
|
||||
};
|
||||
}
|
||||
|
||||
return apiError;
|
||||
}
|
||||
|
||||
/**
|
||||
* post all of the data related to demographics.
|
||||
* @param {Number} userId users are identified in the api by LMS id
|
||||
* @param {Object} commitValues { demographics }
|
||||
*/
|
||||
export async function postDemographics(userId) {
|
||||
const requestConfig = { headers: { 'Content-Type': 'application/json' } };
|
||||
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/`;
|
||||
const commitValues = { user: userId };
|
||||
let data = {};
|
||||
|
||||
({ data } = await getAuthenticatedHttpClient()
|
||||
.post(requestUrl, commitValues, requestConfig)
|
||||
.catch((error) => {
|
||||
const apiError = createDemographicsError(error);
|
||||
throw apiError;
|
||||
}));
|
||||
|
||||
return convertData(data, FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* get all data related to the demographics.
|
||||
* @param {Number} userId users are identified in the api by LMS id
|
||||
*/
|
||||
export async function getDemographics(userId) {
|
||||
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
({ data } = await getAuthenticatedHttpClient()
|
||||
.get(requestUrl));
|
||||
|
||||
data = convertData(data, FROM);
|
||||
} catch (error) {
|
||||
const apiError = Object.create(error);
|
||||
// if the API called resulted in this user receiving a 404 then follow up with a POST call to
|
||||
// try and create the demographics entity on the backend
|
||||
if (apiError.customAttributes.httpErrorStatus) {
|
||||
if (apiError.customAttributes.httpErrorStatus === 404) {
|
||||
data = await postDemographics(userId);
|
||||
}
|
||||
} else {
|
||||
data = {
|
||||
user: userId,
|
||||
demographics_gender: '',
|
||||
demographics_gender_description: '',
|
||||
demographics_income: '',
|
||||
demographics_learner_education_level: '',
|
||||
demographics_parent_education_level: '',
|
||||
demographics_military_history: '',
|
||||
demographics_work_status: '',
|
||||
demographics_work_status_description: '',
|
||||
demographics_current_work_sector: '',
|
||||
demographics_future_work_sector: '',
|
||||
demographics_user_ethnicity: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* patch all of the data related to demographics.
|
||||
* @param {Number} userId users are identified in the api by LMS id
|
||||
* @param {Object} commitValues { demographics }
|
||||
*/
|
||||
export async function patchDemographics(userId, commitValues) {
|
||||
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
|
||||
const convertedCommitValues = convertData(commitValues, TO);
|
||||
let data = {};
|
||||
|
||||
({ data } = await getAuthenticatedHttpClient()
|
||||
.patch(requestUrl, convertedCommitValues)
|
||||
.catch((error) => {
|
||||
const apiError = createDemographicsError(error);
|
||||
throw apiError;
|
||||
}));
|
||||
|
||||
return convertData(data, FROM);
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve the options for each field from the Demographics API
|
||||
*/
|
||||
export async function getDemographicsOptions() {
|
||||
const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/`;
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
({ data } = await getAuthenticatedHttpClient().options(requestUrl));
|
||||
} catch (error) {
|
||||
// We are catching and suppressing errors here on purpose. If an error occurs during the
|
||||
// getDemographicsOptions call we will pass back an empty `data` object. Downstream we make
|
||||
// the assumption that if the demographicsOptions object is empty that there was an issue or
|
||||
// error communicating with the service/API.
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
63
src/account-settings/demographics/data/utils.js
Normal file
63
src/account-settings/demographics/data/utils.js
Normal file
@@ -0,0 +1,63 @@
|
||||
export const TO = 'to';
|
||||
export const FROM = 'from';
|
||||
export const DEMOGRAPHICS_FIELDS = [
|
||||
'demographics_gender',
|
||||
'demographics_gender_description',
|
||||
'demographics_income',
|
||||
'demographics_learner_education_level',
|
||||
'demographics_parent_education_level',
|
||||
'demographics_military_history',
|
||||
'demographics_work_status',
|
||||
'demographics_work_status_description',
|
||||
'demographics_current_work_sector',
|
||||
'demographics_future_work_sector',
|
||||
'demographics_user_ethnicity',
|
||||
];
|
||||
|
||||
// Frontend wants (example):
|
||||
// demographics_user_ethnicity: ["asian", "white", "other"]
|
||||
//
|
||||
// Demographics wants (example):
|
||||
// user_ethnicity: [
|
||||
// { ethnicity: "asian" },
|
||||
// { ethnicity: "white" },
|
||||
// { ethnicity: "other" }
|
||||
// ]
|
||||
function convertEthnicity(ethnicityData, direction) {
|
||||
if (direction === FROM) {
|
||||
return ethnicityData.map(e => e.ethnicity);
|
||||
}
|
||||
|
||||
if (direction === TO) {
|
||||
return ethnicityData.map(e => ({ ethnicity: e }));
|
||||
}
|
||||
|
||||
return ethnicityData;
|
||||
}
|
||||
|
||||
// Handles conversion of data to/from Demographics IDA to/from format needed for
|
||||
// frontend
|
||||
// * handles ethnicity field
|
||||
// * adds/removes 'demographics' to/from key
|
||||
// * replace `null` with empty string or empty string with null
|
||||
export function convertData(dataObject, direction) {
|
||||
const converted = {};
|
||||
|
||||
Object.entries(dataObject).forEach(([key, value]) => {
|
||||
let newValue = value;
|
||||
|
||||
if (key.includes('ethnicity')) {
|
||||
newValue = convertEthnicity(value, direction);
|
||||
}
|
||||
|
||||
if (direction === TO) {
|
||||
converted[key.replace('demographics_', '')] = newValue || null;
|
||||
}
|
||||
|
||||
if (direction === FROM) {
|
||||
converted[`demographics_${key}`] = newValue || '';
|
||||
}
|
||||
});
|
||||
|
||||
return converted;
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
/* eslint-disable no-import-assign */
|
||||
import * as auth from '@edx/frontend-platform/auth';
|
||||
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { Provider } from 'react-redux';
|
||||
import React from 'react';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import renderer from 'react-test-renderer';
|
||||
import DemographicsSection from '../DemographicsSection';
|
||||
|
||||
jest.mock('@edx/frontend-platform/auth');
|
||||
|
||||
const IntlDemographicsSection = injectIntl(DemographicsSection);
|
||||
|
||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ demographicsSectionSelector: () => ({}) })));
|
||||
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('DemographicsSection', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore();
|
||||
props = {
|
||||
updateDraft: jest.fn(),
|
||||
formValues: {
|
||||
demographics_gender: 'declined',
|
||||
demographics_gender_description: '',
|
||||
demographics_user_ethnicity: [],
|
||||
demographics_income: 'declined',
|
||||
demographics_military_history: 'declined',
|
||||
demographics_learner_education_level: 'declined',
|
||||
demographics_parent_education_level: 'declined',
|
||||
demographics_work_status: 'declined',
|
||||
demographics_work_status_description: '',
|
||||
demographics_current_work_sector: 'declined',
|
||||
demographics_future_work_sector: 'declined',
|
||||
demographics_user: 1,
|
||||
demographicsOptions: {
|
||||
actions: {
|
||||
POST: {
|
||||
gender: {
|
||||
choices: [
|
||||
{
|
||||
value: 'woman',
|
||||
display_name: 'Woman',
|
||||
},
|
||||
{
|
||||
value: 'man',
|
||||
display_name: 'Man',
|
||||
},
|
||||
{
|
||||
value: 'non-binary',
|
||||
display_name: 'Non-binary',
|
||||
},
|
||||
{
|
||||
value: 'self-describe',
|
||||
display_name: 'Prefer to self describe',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
income: {
|
||||
choices: [
|
||||
{
|
||||
value: 'less-than-10k',
|
||||
display_name: 'Less than US $10,000',
|
||||
},
|
||||
{
|
||||
value: '10k-25k',
|
||||
display_name: 'US $10,000 - $25,000',
|
||||
},
|
||||
{
|
||||
value: '25k-50k',
|
||||
display_name: 'US $25,000 - $50,000',
|
||||
},
|
||||
{
|
||||
value: '50k-75k',
|
||||
display_name: 'US $50,000 - $75,000',
|
||||
},
|
||||
{
|
||||
value: '75k-100k',
|
||||
display_name: 'US $75,000 - $100,000',
|
||||
},
|
||||
{
|
||||
value: 'over-100k',
|
||||
display_name: 'Over US $100,000',
|
||||
},
|
||||
{
|
||||
value: 'unsure',
|
||||
display_name: "I don't know",
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
learner_education_level: {
|
||||
choices: [
|
||||
{
|
||||
value: 'no-high-school',
|
||||
display_name: 'No High School',
|
||||
},
|
||||
{
|
||||
value: 'some-high-school',
|
||||
display_name: 'Some High School',
|
||||
},
|
||||
{
|
||||
value: 'high-school-ged-equivalent',
|
||||
display_name: 'High School diploma, GED, or equivalent',
|
||||
},
|
||||
{
|
||||
value: 'some-college',
|
||||
display_name: 'Some college, but no degree',
|
||||
},
|
||||
{
|
||||
value: 'associates',
|
||||
display_name: 'Associates degree',
|
||||
},
|
||||
{
|
||||
value: 'bachelors',
|
||||
display_name: 'Bachelors degree',
|
||||
},
|
||||
{
|
||||
value: 'masters',
|
||||
display_name: 'Masters degree',
|
||||
},
|
||||
{
|
||||
value: 'professional',
|
||||
display_name: 'Professional degree',
|
||||
},
|
||||
{
|
||||
value: 'doctorate',
|
||||
display_name: 'Doctorate degree',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
parent_education_level: {
|
||||
choices: [
|
||||
{
|
||||
value: 'no-high-school',
|
||||
display_name: 'No High School',
|
||||
},
|
||||
{
|
||||
value: 'some-high-school',
|
||||
display_name: 'Some High School',
|
||||
},
|
||||
{
|
||||
value: 'high-school-ged-equivalent',
|
||||
display_name: 'High School diploma, GED, or equivalent',
|
||||
},
|
||||
{
|
||||
value: 'some-college',
|
||||
display_name: 'Some college, but no degree',
|
||||
},
|
||||
{
|
||||
value: 'associates',
|
||||
display_name: 'Associates degree',
|
||||
},
|
||||
{
|
||||
value: 'bachelors',
|
||||
display_name: 'Bachelors degree',
|
||||
},
|
||||
{
|
||||
value: 'masters',
|
||||
display_name: 'Masters degree',
|
||||
},
|
||||
{
|
||||
value: 'professional',
|
||||
display_name: 'Professional degree',
|
||||
},
|
||||
{
|
||||
value: 'doctorate',
|
||||
display_name: 'Doctorate degree',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
military_history: {
|
||||
choices: [
|
||||
{
|
||||
value: 'never-served',
|
||||
display_name: 'Never served in the military',
|
||||
},
|
||||
{
|
||||
value: 'training',
|
||||
display_name: 'Only on active duty for training',
|
||||
},
|
||||
{
|
||||
value: 'active',
|
||||
display_name: 'Now on active duty',
|
||||
},
|
||||
{
|
||||
value: 'previously-active',
|
||||
display_name: 'On active duty in the past, but not now',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
work_status: {
|
||||
choices: [
|
||||
{
|
||||
value: 'full-time',
|
||||
display_name: 'Employed, working full-time',
|
||||
},
|
||||
{
|
||||
value: 'part-time',
|
||||
display_name: 'Employed, working part-time',
|
||||
},
|
||||
{
|
||||
value: 'self-employed',
|
||||
display_name: 'Self-Employed',
|
||||
},
|
||||
{
|
||||
value: 'not-employed-looking',
|
||||
display_name: 'Not employed, looking for work',
|
||||
},
|
||||
{
|
||||
value: 'not-employed-not-looking',
|
||||
display_name: 'Not employed, not looking for work',
|
||||
},
|
||||
{
|
||||
value: 'unable',
|
||||
display_name: 'Unable to work',
|
||||
},
|
||||
{
|
||||
value: 'retired',
|
||||
display_name: 'Retired',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
display_name: 'Other',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
current_work_sector: {
|
||||
choices: [
|
||||
{
|
||||
value: 'accommodation-food',
|
||||
display_name: 'Accommodation and Food Services',
|
||||
},
|
||||
{
|
||||
value: 'administrative-support-waste-remediation',
|
||||
display_name: 'Administrative and Support and Waste Management and Remediation Services',
|
||||
},
|
||||
{
|
||||
value: 'agriculture-forestry-fishing-hunting',
|
||||
display_name: 'Agriculture, Forestry, Fishing and Hunting',
|
||||
},
|
||||
{
|
||||
value: 'arts-entertainment-recreation',
|
||||
display_name: 'Arts, Entertainment, and Recreation',
|
||||
},
|
||||
{
|
||||
value: 'construction',
|
||||
display_name: 'Construction',
|
||||
},
|
||||
{
|
||||
value: 'educational',
|
||||
display_name: 'Education Services',
|
||||
},
|
||||
{
|
||||
value: 'finance-insurance',
|
||||
display_name: 'Finance and Insurance',
|
||||
},
|
||||
{
|
||||
value: 'healthcare-social',
|
||||
display_name: 'Health Care and Social Assistance',
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
display_name: 'Information',
|
||||
},
|
||||
{
|
||||
value: 'management',
|
||||
display_name: 'Management of Companies and Enterprises',
|
||||
},
|
||||
{
|
||||
value: 'manufacturing',
|
||||
display_name: 'Manufacturing',
|
||||
},
|
||||
{
|
||||
value: 'mining-quarry-oil-gas',
|
||||
display_name: 'Mining, Quarrying, and Oil and Gas Extraction',
|
||||
},
|
||||
{
|
||||
value: 'professional-scientific-technical',
|
||||
display_name: 'Professional, Scientific, and Technical Services',
|
||||
},
|
||||
{
|
||||
value: 'public-admin',
|
||||
display_name: 'Public Administration',
|
||||
},
|
||||
{
|
||||
value: 'real-estate',
|
||||
display_name: 'Real Estate and Rental and Leasing',
|
||||
},
|
||||
{
|
||||
value: 'retail',
|
||||
display_name: 'Retail Trade',
|
||||
},
|
||||
{
|
||||
value: 'transport-warehousing',
|
||||
display_name: 'Transportation and Warehousing',
|
||||
},
|
||||
{
|
||||
value: 'utilities',
|
||||
display_name: 'Utilities',
|
||||
},
|
||||
{
|
||||
value: 'trade',
|
||||
display_name: 'Wholesale Trade',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
display_name: 'Other',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
future_work_sector: {
|
||||
choices: [
|
||||
{
|
||||
value: 'accommodation-food',
|
||||
display_name: 'Accommodation and Food Services',
|
||||
},
|
||||
{
|
||||
value: 'administrative-support-waste-remediation',
|
||||
display_name: 'Administrative and Support and Waste Management and Remediation Services',
|
||||
},
|
||||
{
|
||||
value: 'agriculture-forestry-fishing-hunting',
|
||||
display_name: 'Agriculture, Forestry, Fishing and Hunting',
|
||||
},
|
||||
{
|
||||
value: 'arts-entertainment-recreation',
|
||||
display_name: 'Arts, Entertainment, and Recreation',
|
||||
},
|
||||
{
|
||||
value: 'construction',
|
||||
display_name: 'Construction',
|
||||
},
|
||||
{
|
||||
value: 'educational',
|
||||
display_name: 'Education Services',
|
||||
},
|
||||
{
|
||||
value: 'finance-insurance',
|
||||
display_name: 'Finance and Insurance',
|
||||
},
|
||||
{
|
||||
value: 'healthcare-social',
|
||||
display_name: 'Health Care and Social Assistance',
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
display_name: 'Information',
|
||||
},
|
||||
{
|
||||
value: 'management',
|
||||
display_name: 'Management of Companies and Enterprises',
|
||||
},
|
||||
{
|
||||
value: 'manufacturing',
|
||||
display_name: 'Manufacturing',
|
||||
},
|
||||
{
|
||||
value: 'mining-quarry-oil-gas',
|
||||
display_name: 'Mining, Quarrying, and Oil and Gas Extraction',
|
||||
},
|
||||
{
|
||||
value: 'professional-scientific-technical',
|
||||
display_name: 'Professional, Scientific, and Technical Services',
|
||||
},
|
||||
{
|
||||
value: 'public-admin',
|
||||
display_name: 'Public Administration',
|
||||
},
|
||||
{
|
||||
value: 'real-estate',
|
||||
display_name: 'Real Estate and Rental and Leasing',
|
||||
},
|
||||
{
|
||||
value: 'retail',
|
||||
display_name: 'Retail Trade',
|
||||
},
|
||||
{
|
||||
value: 'transport-warehousing',
|
||||
display_name: 'Transportation and Warehousing',
|
||||
},
|
||||
{
|
||||
value: 'utilities',
|
||||
display_name: 'Utilities',
|
||||
},
|
||||
{
|
||||
value: 'trade',
|
||||
display_name: 'Wholesale Trade',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
display_name: 'Other',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
user_ethnicity: {
|
||||
child: {
|
||||
children: {
|
||||
ethnicity: {
|
||||
choices: [
|
||||
{
|
||||
value: 'american-indian-or-alaska-native',
|
||||
display_name: 'American Indian or Alaska Native',
|
||||
},
|
||||
{
|
||||
value: 'asian',
|
||||
display_name: 'Asian',
|
||||
},
|
||||
{
|
||||
value: 'black-or-african-american',
|
||||
display_name: 'Black or African American',
|
||||
},
|
||||
{
|
||||
value: 'hispanic-latin-spanish',
|
||||
display_name: 'Hispanic, Latin, or Spanish origin',
|
||||
},
|
||||
{
|
||||
value: 'middle-eastern-or-north-african',
|
||||
display_name: 'Middle Eastern or North African',
|
||||
},
|
||||
{
|
||||
value: 'native-hawaiian-or-pacific-islander',
|
||||
display_name: 'Native Hawaiian or Other Pacific Islander',
|
||||
},
|
||||
{
|
||||
value: 'white',
|
||||
display_name: 'White',
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
display_name: 'Some other race, ethnicity, or origin',
|
||||
},
|
||||
{
|
||||
value: 'declined',
|
||||
display_name: 'Prefer not to respond',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
formErrors: {},
|
||||
intl: {},
|
||||
forwardRef: () => {},
|
||||
drafts: {},
|
||||
};
|
||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||
patch: async () => ({
|
||||
data: { status: 200 },
|
||||
catch: () => {},
|
||||
}),
|
||||
}));
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 1 }));
|
||||
});
|
||||
|
||||
it('should render', () => {
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render an Alert if an error occurs', () => {
|
||||
props = {
|
||||
...props,
|
||||
formErrors: {
|
||||
demographicsError: 'api-error',
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set user input correctly when user provides gender self-description', () => {
|
||||
props = {
|
||||
...props,
|
||||
formValues: {
|
||||
...props.formValues,
|
||||
demographics_gender: 'self-describe',
|
||||
demographics_gender_description: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set user input correctly when user provides answers to work_status question', () => {
|
||||
props = {
|
||||
...props,
|
||||
formValues: {
|
||||
...props.formValues,
|
||||
demographics_work_status: 'other',
|
||||
demographics_work_status_description: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render ethnicity text correctly', () => {
|
||||
props = {
|
||||
...props,
|
||||
formValues: {
|
||||
...props.formValues,
|
||||
demographics_user_ethnicity: ['asian'],
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render ethnicity correctly when multiple options are selected', () => {
|
||||
props = {
|
||||
...props,
|
||||
formValues: {
|
||||
...props.formValues,
|
||||
demographics_user_ethnicity: ['hispanic-latin-spanish', 'white'],
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render an Alert when demographicsOptions props are empty', () => {
|
||||
props = {
|
||||
...props,
|
||||
formValues: {
|
||||
demographicsOptions: null,
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = renderer.create(reduxWrapper(<IntlDemographicsSection {...props} />)).toJSON();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3946 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DemographicsSection should render 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
<button
|
||||
className="p-0 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Add race/ethnicity identity
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should render an Alert if an error occurs 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert alert-danger"
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
<button
|
||||
className="p-0 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Add race/ethnicity identity
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should render an Alert when demographicsOptions props are empty 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
tabIndex="-1"
|
||||
>
|
||||
<div
|
||||
className="alert d-flex align-items-start alert alert-danger"
|
||||
>
|
||||
<div />
|
||||
<div>
|
||||
An error occurred attempting to retrieve or save your account information. Please try again later.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should render ethnicity correctly when multiple options are selected 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Hispanic, Latin, or Spanish origin, White
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should render ethnicity text correctly 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Asian
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should set user input correctly when user provides answers to work_status question 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
<button
|
||||
className="p-0 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Add race/ethnicity identity
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Other: test
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`DemographicsSection should set user input correctly when user provides gender self-description 1`] = `
|
||||
<div
|
||||
className="account-section pt-3 mb-5"
|
||||
id="demographics-information"
|
||||
>
|
||||
<h2
|
||||
className="section-heading h4 mb-3"
|
||||
>
|
||||
Optional Information
|
||||
</h2>
|
||||
<p>
|
||||
<a
|
||||
className="pgn__hyperlink default-link standalone-link"
|
||||
href="http://localhost:5335/demographics"
|
||||
onClick={[Function]}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Why does localhost collect this information?
|
||||
<span
|
||||
className="pgn__hyperlink__external-icon"
|
||||
title="Opens in a new tab"
|
||||
>
|
||||
<span
|
||||
className="pgn__icon"
|
||||
style={
|
||||
Object {
|
||||
"height": "1em",
|
||||
"width": "1em",
|
||||
}
|
||||
}
|
||||
>
|
||||
<svg
|
||||
aria-hidden={true}
|
||||
fill="none"
|
||||
focusable={false}
|
||||
height={24}
|
||||
role="img"
|
||||
viewBox="0 0 24 24"
|
||||
width={24}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19 19H5V5h7V3H3v18h18v-9h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
className="sr-only"
|
||||
>
|
||||
in a new tab
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
id="demographics-fields"
|
||||
>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Gender identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer to self describe: test
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Race/Ethnicity identity
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
<button
|
||||
className="p-0 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Add race/ethnicity identity
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Family income
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
U.S. Military status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Your education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Parents/Guardians education level
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Employment status
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Current work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="pgn-transition-replace-group position-relative"
|
||||
style={
|
||||
Object {
|
||||
"height": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
style={
|
||||
Object {
|
||||
"padding": ".1px 0",
|
||||
}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="form-group"
|
||||
>
|
||||
<div
|
||||
className="d-flex align-items-start"
|
||||
>
|
||||
<h6
|
||||
aria-level="3"
|
||||
>
|
||||
Future work industry
|
||||
</h6>
|
||||
<button
|
||||
className="ml-3 btn btn-link"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
|
||||
data-icon="pencil-alt"
|
||||
data-prefix="fas"
|
||||
focusable="false"
|
||||
role="img"
|
||||
style={Object {}}
|
||||
viewBox="0 0 512 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
|
||||
fill="currentColor"
|
||||
style={Object {}}
|
||||
/>
|
||||
</svg>
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
<p
|
||||
className={null}
|
||||
data-hj-suppress={true}
|
||||
>
|
||||
Prefer not to respond
|
||||
</p>
|
||||
<p
|
||||
className="small text-muted mt-n2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,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';
|
||||
|
||||
203
src/account-settings/name-change/NameChange.jsx
Normal file
203
src/account-settings/name-change/NameChange.jsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { connect, useDispatch } from 'react-redux';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import {
|
||||
ActionRow,
|
||||
Alert,
|
||||
Button,
|
||||
Col,
|
||||
Form,
|
||||
ModalDialog,
|
||||
StatefulButton,
|
||||
} from '@edx/paragon';
|
||||
|
||||
import { closeForm, saveSettingsReset } from '../data/actions';
|
||||
import { nameChangeSelector } from '../data/selectors';
|
||||
|
||||
import { requestNameChange, requestNameChangeFailure, requestNameChangeReset } from './data/actions';
|
||||
import messages from './messages';
|
||||
|
||||
const NameChangeModal = ({
|
||||
targetFormId,
|
||||
errors,
|
||||
formValues,
|
||||
intl,
|
||||
saveState,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { push } = useHistory();
|
||||
const { username } = getAuthenticatedUser();
|
||||
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
||||
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
||||
|
||||
const resetLocalState = useCallback(() => {
|
||||
setConfirmedWarning(false);
|
||||
dispatch(requestNameChangeReset());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setVerifiedNameInput(e.target.value);
|
||||
};
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
resetLocalState();
|
||||
dispatch(closeForm(targetFormId));
|
||||
dispatch(saveSettingsReset());
|
||||
}, [dispatch, resetLocalState, targetFormId]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (saveState === 'pending') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!verifiedNameInput) {
|
||||
dispatch(requestNameChangeFailure({
|
||||
verified_name: intl.formatMessage(messages['account.settings.name.change.error.valid.name']),
|
||||
}));
|
||||
} else {
|
||||
const draftProfileName = targetFormId === 'name' ? formValues.name : null;
|
||||
dispatch(requestNameChange(username, draftProfileName, verifiedNameInput));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (saveState === 'complete') {
|
||||
handleClose();
|
||||
push(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
||||
}
|
||||
}, [handleClose, push, saveState]);
|
||||
|
||||
function renderErrors() {
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return (
|
||||
<>
|
||||
{Object.entries(errors).map(([key, value]) => (
|
||||
<Form.Control.Feedback type="invalid" key={key}>
|
||||
{
|
||||
key === 'general_error'
|
||||
? intl.formatMessage(messages['account.settings.name.change.error.general'])
|
||||
: value
|
||||
}
|
||||
</Form.Control.Feedback>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderTitle() {
|
||||
if (!confirmedWarning) {
|
||||
return intl.formatMessage(messages['account.settings.name.change.title.id']);
|
||||
}
|
||||
|
||||
return intl.formatMessage(messages['account.settings.name.change.title.begin']);
|
||||
}
|
||||
|
||||
function renderBody() {
|
||||
if (!confirmedWarning) {
|
||||
return (
|
||||
<Alert variant="warning">
|
||||
<p>
|
||||
{intl.formatMessage(messages['account.settings.name.change.warning.one'])}
|
||||
</p>
|
||||
<p>
|
||||
{intl.formatMessage(messages['account.settings.name.change.warning.two'])}
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Group as={Col} isInvalid={Object.keys(errors).length > 0}>
|
||||
<Form.Label>
|
||||
{intl.formatMessage(messages['account.settings.name.change.id.name.label'])}
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
type="text"
|
||||
name="verifiedName"
|
||||
placeholder={intl.formatMessage(messages['account.settings.name.change.id.name.placeholder'])}
|
||||
value={verifiedNameInput}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{renderErrors()}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function renderContinueButton() {
|
||||
if (!confirmedWarning) {
|
||||
return (
|
||||
<Button variant="primary" onClick={() => setConfirmedWarning(true)}>
|
||||
{intl.formatMessage(messages['account.settings.name.change.continue'])}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StatefulButton
|
||||
type="submit"
|
||||
state={saveState}
|
||||
labels={{
|
||||
default: intl.formatMessage(messages['account.settings.name.change.continue']),
|
||||
}}
|
||||
disabledStates={[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
title={renderTitle()}
|
||||
isOpen
|
||||
hasCloseButton={false}
|
||||
onClose={handleClose}
|
||||
>
|
||||
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<ModalDialog.Header>
|
||||
<ModalDialog.Title>
|
||||
{renderTitle()}
|
||||
</ModalDialog.Title>
|
||||
</ModalDialog.Header>
|
||||
|
||||
<ModalDialog.Body className="mb-3 overflow-hidden">
|
||||
{renderBody()}
|
||||
</ModalDialog.Body>
|
||||
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<ModalDialog.CloseButton variant="tertiary">
|
||||
{intl.formatMessage(messages['account.settings.name.change.cancel'])}
|
||||
</ModalDialog.CloseButton>
|
||||
{renderContinueButton()}
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</Form>
|
||||
|
||||
</ModalDialog>
|
||||
);
|
||||
};
|
||||
|
||||
NameChangeModal.propTypes = {
|
||||
targetFormId: PropTypes.string.isRequired,
|
||||
errors: PropTypes.shape({}).isRequired,
|
||||
formValues: PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
verified_name: PropTypes.string,
|
||||
}).isRequired,
|
||||
saveState: PropTypes.string,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
NameChangeModal.defaultProps = {
|
||||
saveState: null,
|
||||
};
|
||||
|
||||
export default connect(nameChangeSelector)(injectIntl(NameChangeModal));
|
||||
25
src/account-settings/name-change/data/actions.js
Normal file
25
src/account-settings/name-change/data/actions.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const REQUEST_NAME_CHANGE = new AsyncActionType('ACCOUNT_SETTINGS', 'REQUEST_NAME_CHANGE');
|
||||
|
||||
export const requestNameChange = (username, profileName, verifiedName) => ({
|
||||
type: REQUEST_NAME_CHANGE.BASE,
|
||||
payload: { username, profileName, verifiedName },
|
||||
});
|
||||
|
||||
export const requestNameChangeBegin = () => ({
|
||||
type: REQUEST_NAME_CHANGE.BEGIN,
|
||||
});
|
||||
|
||||
export const requestNameChangeSuccess = () => ({
|
||||
type: REQUEST_NAME_CHANGE.SUCCESS,
|
||||
});
|
||||
|
||||
export const requestNameChangeFailure = errors => ({
|
||||
type: REQUEST_NAME_CHANGE.FAILURE,
|
||||
payload: { errors },
|
||||
});
|
||||
|
||||
export const requestNameChangeReset = () => ({
|
||||
type: REQUEST_NAME_CHANGE.RESET,
|
||||
});
|
||||
44
src/account-settings/name-change/data/reducers.js
Normal file
44
src/account-settings/name-change/data/reducers.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { REQUEST_NAME_CHANGE } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
saveState: null,
|
||||
errors: {},
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action = null) => {
|
||||
if (action !== null) {
|
||||
switch (action.type) {
|
||||
case REQUEST_NAME_CHANGE.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
saveState: 'pending',
|
||||
errors: {},
|
||||
};
|
||||
|
||||
case REQUEST_NAME_CHANGE.SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
saveState: 'complete',
|
||||
};
|
||||
|
||||
case REQUEST_NAME_CHANGE.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
saveState: 'error',
|
||||
errors: action.payload.errors || { general_error: 'A technical error occurred. Please try again.' },
|
||||
};
|
||||
|
||||
case REQUEST_NAME_CHANGE.RESET:
|
||||
return {
|
||||
...state,
|
||||
saveState: null,
|
||||
errors: {},
|
||||
};
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default reducer;
|
||||
40
src/account-settings/name-change/data/sagas.js
Normal file
40
src/account-settings/name-change/data/sagas.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { put, call, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { postVerifiedName } from '../../data/service';
|
||||
|
||||
import {
|
||||
REQUEST_NAME_CHANGE,
|
||||
requestNameChangeBegin,
|
||||
requestNameChangeSuccess,
|
||||
requestNameChangeFailure,
|
||||
} from './actions';
|
||||
import { postNameChange } from './service';
|
||||
|
||||
export function* handleRequestNameChange(action) {
|
||||
let { name: profileName } = getAuthenticatedUser();
|
||||
try {
|
||||
yield put(requestNameChangeBegin());
|
||||
if (action.payload.profileName) {
|
||||
yield call(postNameChange, action.payload.profileName);
|
||||
profileName = action.payload.profileName;
|
||||
}
|
||||
yield call(postVerifiedName, {
|
||||
username: action.payload.username,
|
||||
verified_name: action.payload.verifiedName,
|
||||
profile_name: profileName,
|
||||
});
|
||||
yield put(requestNameChangeSuccess());
|
||||
} catch (err) {
|
||||
if (err.customAttributes?.httpErrorResponseData) {
|
||||
yield put(requestNameChangeFailure(JSON.parse(err.customAttributes.httpErrorResponseData)));
|
||||
} else {
|
||||
yield put(requestNameChangeFailure());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function* saga() {
|
||||
yield takeEvery(REQUEST_NAME_CHANGE.BASE, handleRequestNameChange);
|
||||
}
|
||||
17
src/account-settings/name-change/data/service.js
Normal file
17
src/account-settings/name-change/data/service.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
import { handleRequestError } from '../../data/utils';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function postNameChange(name) {
|
||||
// Requests a pending name change, rather than saving the account name immediately
|
||||
const requestConfig = { headers: { Accept: 'application/json' } };
|
||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/user/v1/accounts/name_change/`;
|
||||
|
||||
const { data } = await getAuthenticatedHttpClient()
|
||||
.post(requestUrl, { name }, requestConfig)
|
||||
.catch(error => handleRequestError(error));
|
||||
|
||||
return data;
|
||||
}
|
||||
5
src/account-settings/name-change/index.js
Normal file
5
src/account-settings/name-change/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// 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';
|
||||
export { REQUEST_NAME_CHANGE } from './data/actions';
|
||||
56
src/account-settings/name-change/messages.js
Normal file
56
src/account-settings/name-change/messages.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'account.settings.name.change.title.id': {
|
||||
id: 'account.settings.name.change.title.id',
|
||||
defaultMessage: 'This name change requires identity verification',
|
||||
description: 'Inform the user that changing their name requires identity verification',
|
||||
},
|
||||
'account.settings.name.change.title.begin': {
|
||||
id: 'account.settings.name.change.title.begin',
|
||||
defaultMessage: 'Before we begin',
|
||||
description: 'Title before beginning the ID verification process',
|
||||
},
|
||||
'account.settings.name.change.warning.one': {
|
||||
id: 'account.settings.name.change.warning.one',
|
||||
defaultMessage: '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.',
|
||||
description: 'Warning informing the user that a name change will update the name on all of their certificates.',
|
||||
},
|
||||
'account.settings.name.change.warning.two': {
|
||||
id: 'account.settings.name.change.warning.two',
|
||||
defaultMessage: 'This action cannot be undone without verifying your identity.',
|
||||
description: 'Warning informing the user that a name change cannot be undone without ID verification.',
|
||||
},
|
||||
'account.settings.name.change.id.name.label': {
|
||||
id: 'account.settings.name.change.id.name.label',
|
||||
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 photo ID',
|
||||
description: 'Form label instructing the user to enter the name on their ID.',
|
||||
},
|
||||
'account.settings.name.change.error.valid.name': {
|
||||
id: 'account.settings.name.change.error.valid.name',
|
||||
defaultMessage: 'Please enter a valid name.',
|
||||
description: 'Error that appears when the user doesn’t enter a valid name.',
|
||||
},
|
||||
'account.settings.name.change.error.general': {
|
||||
id: 'account.settings.name.change.error.general',
|
||||
defaultMessage: 'A technical error occurred. Please try again.',
|
||||
description: 'Generic error message.',
|
||||
},
|
||||
'account.settings.name.change.continue': {
|
||||
id: 'account.settings.name.change.continue',
|
||||
defaultMessage: 'Continue',
|
||||
description: 'Continue button.',
|
||||
},
|
||||
'account.settings.name.change.cancel': {
|
||||
id: 'account.settings.name.change.cancel',
|
||||
defaultMessage: 'Cancel',
|
||||
description: 'Cancel button.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
173
src/account-settings/name-change/test/NameChange.test.jsx
Normal file
173
src/account-settings/name-change/test/NameChange.test.jsx
Normal file
@@ -0,0 +1,173 @@
|
||||
/* 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 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';
|
||||
|
||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
||||
ReactDOM.createPortal = node => node;
|
||||
|
||||
import NameChange from '../NameChange'; // eslint-disable-line import/first
|
||||
|
||||
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(() => ({ nameChangeSelector: () => ({}) })));
|
||||
|
||||
const history = createMemoryHistory();
|
||||
|
||||
const IntlNameChange = injectIntl(NameChange);
|
||||
|
||||
const mockStore = configureStore();
|
||||
|
||||
describe('NameChange', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<Router history={history}>
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
store = mockStore();
|
||||
props = {
|
||||
targetFormId: 'test_form',
|
||||
errors: {},
|
||||
formValues: {
|
||||
name: 'edx edx',
|
||||
verified_name: 'edX Verified',
|
||||
},
|
||||
saveState: null,
|
||||
intl: {},
|
||||
};
|
||||
|
||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||
patch: async () => ({
|
||||
data: { status: 200 },
|
||||
catch: () => {},
|
||||
}),
|
||||
}));
|
||||
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3, username: 'edx' }));
|
||||
});
|
||||
|
||||
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 photo ID');
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
expect(getInput()).toBeNull();
|
||||
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
expect(getInput().value).toBe('edX Verified');
|
||||
});
|
||||
|
||||
it('renders empty input after clicking continue if verified_name not in form data', async () => {
|
||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
||||
const formProps = {
|
||||
...props,
|
||||
formValues: {
|
||||
name: 'edx edx',
|
||||
},
|
||||
};
|
||||
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
||||
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
expect(getInput().value).toBe('');
|
||||
});
|
||||
|
||||
it('dispatches verifiedName on submit if targetForm is not "name"', async () => {
|
||||
const dispatchData = {
|
||||
payload: {
|
||||
profileName: null,
|
||||
username: 'edx',
|
||||
verifiedName: 'Verified Name',
|
||||
},
|
||||
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
||||
};
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
fireEvent.click(submitButton);
|
||||
expect(mockDispatch).toHaveBeenCalledWith(dispatchData);
|
||||
});
|
||||
|
||||
it('dispatches both profileName and verifiedName on submit if the targetForm is "name"', async () => {
|
||||
const dispatchData = {
|
||||
payload: {
|
||||
profileName: 'edx edx',
|
||||
username: 'edx',
|
||||
verifiedName: 'Verified Name',
|
||||
},
|
||||
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
||||
};
|
||||
const formProps = {
|
||||
...props,
|
||||
targetFormId: 'name',
|
||||
};
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
||||
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
fireEvent.click(submitButton);
|
||||
expect(mockDispatch).toHaveBeenCalledWith(dispatchData);
|
||||
});
|
||||
|
||||
it('does not dispatch action while pending', async () => {
|
||||
props.saveState = 'pending';
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
|
||||
const continueButton = screen.getByText('Continue');
|
||||
fireEvent.click(continueButton);
|
||||
|
||||
const input = screen.getByPlaceholderText('Enter the name on your photo ID');
|
||||
fireEvent.change(input, { target: { value: 'Verified Name' } });
|
||||
|
||||
const submitButton = screen.getByText('Continue');
|
||||
fireEvent.click(submitButton);
|
||||
expect(mockDispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('routes to IDV when name change request is successful', async () => {
|
||||
props.saveState = 'complete';
|
||||
|
||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
||||
expect(history.location.pathname).toEqual('/id-verification');
|
||||
});
|
||||
});
|
||||
@@ -5,20 +5,17 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import Alert from '../Alert';
|
||||
|
||||
const RequestInProgressAlert = (props) => {
|
||||
|
||||
return (
|
||||
<Alert
|
||||
className="alert-warning mt-n2"
|
||||
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="account.settings.editable.field.password.reset.button.forbidden"
|
||||
defaultMessage="Your previous request is in progress, please try again in few moments."
|
||||
description="A message displayed when a previous password reset request is still in progress."
|
||||
/>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
const RequestInProgressAlert = () => (
|
||||
<Alert
|
||||
className="alert-warning mt-n2"
|
||||
icon={<FontAwesomeIcon className="mr-2" icon={faExclamationTriangle} />}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="account.settings.editable.field.password.reset.button.forbidden"
|
||||
defaultMessage="Your previous request is in progress, please try again in few moments."
|
||||
description="A message displayed when a previous password reset request is still in progress."
|
||||
/>
|
||||
</Alert>
|
||||
);
|
||||
|
||||
export default RequestInProgressAlert;
|
||||
|
||||
@@ -22,7 +22,7 @@ const ResetPassword = (props) => {
|
||||
</h6>
|
||||
<p>
|
||||
<StatefulButton
|
||||
className="btn-link"
|
||||
variant="link"
|
||||
state={status}
|
||||
onClick={(e) => {
|
||||
// Swallow clicks if the state is pending.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { put, call, takeEvery } from 'redux-saga/effects';
|
||||
|
||||
import { resetPasswordBegin, resetPasswordForbidden, resetPasswordSuccess, RESET_PASSWORD } from './actions';
|
||||
import {
|
||||
resetPasswordBegin, resetPasswordForbidden, resetPasswordSuccess, RESET_PASSWORD,
|
||||
} from './actions';
|
||||
import { postResetPassword } from './service';
|
||||
|
||||
function* handleResetPassword(action) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -12,9 +12,8 @@ export const siteLanguageListSelector = createSelector(
|
||||
|
||||
export const siteLanguageOptionsSelector = createSelector(
|
||||
siteLanguageSelector,
|
||||
siteLanguage =>
|
||||
siteLanguage.siteLanguageList.map(({ code, name }) => ({
|
||||
value: code,
|
||||
label: name,
|
||||
})),
|
||||
siteLanguage => siteLanguage.siteLanguageList.map(({ code, name }) => ({
|
||||
value: code,
|
||||
label: name,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils';
|
||||
import siteLanguageList from './constants';
|
||||
import { snakeCaseObject, convertKeyNames } from '../data/utils';
|
||||
|
||||
export async function getSiteLanguageList() {
|
||||
return siteLanguageList;
|
||||
|
||||
62
src/account-settings/test/JumpNav.test.jsx
Normal file
62
src/account-settings/test/JumpNav.test.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { mergeConfig, setConfig } from '@edx/frontend-platform';
|
||||
import JumpNav from '../JumpNav';
|
||||
|
||||
const IntlJumpNav = injectIntl(JumpNav);
|
||||
|
||||
describe('JumpNav', () => {
|
||||
mergeConfig({
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION: false,
|
||||
});
|
||||
|
||||
let props = {};
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
intl: {},
|
||||
displayDemographicsLink: false,
|
||||
};
|
||||
});
|
||||
|
||||
it('should not render Optional Information link', () => {
|
||||
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">
|
||||
<IntlJumpNav {...props} />
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render Optional Information link', () => {
|
||||
setConfig({
|
||||
ENABLE_DEMOGRAPHICS_COLLECTION: true,
|
||||
});
|
||||
|
||||
props = {
|
||||
...props,
|
||||
displayDemographicsLink: true,
|
||||
};
|
||||
|
||||
const tree = renderer.create((
|
||||
// Same as previous test
|
||||
<Router>
|
||||
<IntlProvider locale="en">
|
||||
<IntlJumpNav {...props} />
|
||||
</IntlProvider>
|
||||
</Router>
|
||||
))
|
||||
.toJSON();
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
194
src/account-settings/test/__snapshots__/JumpNav.test.jsx.snap
Normal file
194
src/account-settings/test/__snapshots__/JumpNav.test.jsx.snap
Normal file
@@ -0,0 +1,194 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`JumpNav should not render Optional Information link 1`] = `
|
||||
<div
|
||||
className="jump-nav jump-nav-sm position-sticky pt-3"
|
||||
>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
onClick={[Function]}
|
||||
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>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`JumpNav should render Optional Information link 1`] = `
|
||||
<div
|
||||
className="jump-nav jump-nav-sm position-sticky pt-3"
|
||||
>
|
||||
<ul
|
||||
className="list-unstyled"
|
||||
style={Object {}}
|
||||
>
|
||||
<li
|
||||
className=""
|
||||
>
|
||||
<a
|
||||
aria-current="page"
|
||||
className="active"
|
||||
href="/#basic-information"
|
||||
onClick={[Function]}
|
||||
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>
|
||||
</div>
|
||||
`;
|
||||
@@ -11,14 +11,16 @@ class ThirdPartyAuth extends Component {
|
||||
onClickDisconnect = (e) => {
|
||||
e.preventDefault();
|
||||
const providerId = e.currentTarget.getAttribute('data-provider-id');
|
||||
if (this.props.disconnectionStatuses[providerId] === 'pending') return;
|
||||
if (this.props.disconnectionStatuses[providerId] === 'pending') {
|
||||
return;
|
||||
}
|
||||
const disconnectUrl = e.currentTarget.getAttribute('data-disconnect-url');
|
||||
this.props.disconnectAuth(disconnectUrl, providerId);
|
||||
}
|
||||
};
|
||||
|
||||
renderUnconnectedProvider(url, name) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<h6 aria-level="3">{name}</h6>
|
||||
<Hyperlink destination={url} className="btn btn-outline-primary">
|
||||
<FormattedMessage
|
||||
@@ -28,7 +30,7 @@ class ThirdPartyAuth extends Component {
|
||||
values={{ name }}
|
||||
/>
|
||||
</Hyperlink>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@ class ThirdPartyAuth extends Component {
|
||||
const hasError = this.props.errors[id];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<>
|
||||
<h6 aria-level="3">
|
||||
{name}
|
||||
<span className="small font-weight-normal text-muted ml-2">
|
||||
@@ -58,7 +60,7 @@ class ThirdPartyAuth extends Component {
|
||||
) : null}
|
||||
|
||||
<StatefulButton
|
||||
className="btn-link"
|
||||
variant="link"
|
||||
state={this.props.disconnectionStatuses[id]}
|
||||
labels={{
|
||||
default: (
|
||||
@@ -75,7 +77,7 @@ class ThirdPartyAuth extends Component {
|
||||
data-disconnect-url={url}
|
||||
data-provider-id={id}
|
||||
/>
|
||||
</React.Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,9 +87,9 @@ class ThirdPartyAuth extends Component {
|
||||
return (
|
||||
<div className="form-group" key={id}>
|
||||
{
|
||||
connected ?
|
||||
this.renderConnectedProvider(disconnectUrl, name, id) :
|
||||
this.renderUnconnectedProvider(connectUrl, name)
|
||||
connected
|
||||
? this.renderConnectedProvider(disconnectUrl, name, id)
|
||||
: this.renderUnconnectedProvider(connectUrl, name)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@@ -98,13 +100,15 @@ class ThirdPartyAuth extends Component {
|
||||
<FormattedMessage
|
||||
id="account.settings.sso.no.providers"
|
||||
defaultMessage="No accounts can be linked at this time."
|
||||
description="Displayed when no third party accounts are available to link an edX account to"
|
||||
description="Displayed when no third-party accounts are available for the user to link to their account on the platform."
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.providers === undefined) return null;
|
||||
if (this.props.providers === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.props.providers.length === 0) {
|
||||
return this.renderNoProviders();
|
||||
@@ -114,7 +118,6 @@ class ThirdPartyAuth extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ThirdPartyAuth.propTypes = {
|
||||
providers: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
|
||||
@@ -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';
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 34 KiB |
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';
|
||||
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 { mount } from 'enzyme';
|
||||
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', () => {
|
||||
mount(<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;
|
||||
55
src/hooks.js
Normal file
55
src/hooks.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
IDLE_STATUS, LOADING_STATUS, SUCCESS_STATUS, FAILURE_STATUS,
|
||||
} from './constants';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function useAsyncCall(asyncFunc) {
|
||||
// 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 () => {
|
||||
setData(currData => ({ ...currData, status: LOADING_STATUS }));
|
||||
const response = await asyncFunc();
|
||||
|
||||
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 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;
|
||||
}
|
||||
@@ -1,135 +1,359 @@
|
||||
{
|
||||
"account.settings.message.duplicate.tpa.provider": "The {provider} account you selected is already linked to another edX 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.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 edX.",
|
||||
"account.settings.field.username": "Username",
|
||||
"account.settings.field.username.help.text": "The name that identifies you on edX. 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.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 edX 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.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.o": "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 languages",
|
||||
"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 edX 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.",
|
||||
"account.settings.coaching.consent.welcome.header": "Let’s get started.",
|
||||
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
|
||||
"account.settings.coaching.consent.description": "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.*",
|
||||
"account.settings.coaching.consent.text-messaging.disclaimer": "* 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.",
|
||||
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
|
||||
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
|
||||
"account.settings.coaching.consent.label.name": "Please confirm your name",
|
||||
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
|
||||
"account.settings.coaching.consent.success.header": "Success!",
|
||||
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You will receive a text message confirmation.",
|
||||
"account.settings.coaching.consent.success.continue": "Start my course",
|
||||
"account.settings.coaching.managed.support": "support",
|
||||
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
|
||||
"account.settings.field.phone_number": "Phone Number",
|
||||
"account.settings.field.phone_number.empty": "Add a phone number",
|
||||
"account.settings.field.coaching_consent": "Coaching consent",
|
||||
"account.settings.field.coaching_consent.tooltip": "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.",
|
||||
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
|
||||
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
|
||||
"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. edX 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 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 the 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 edX.",
|
||||
"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.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. edX 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 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.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.delete.account.text.3": "You may also lose access to verified certificates and other program credentials like MicroMasters certificates. If you want to make a copy of these for your records before proceeding with deletion, {actionLink}.",
|
||||
"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.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": "Reset Password",
|
||||
"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.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."
|
||||
"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.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.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": "يمكنك وصل حسابات هويتك لتبسيط تسجيل دخولك إلى {siteName}.",
|
||||
"account.settings.field.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.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": "على هذا العنوان تصلك الرسائل من {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.dob.month": "الشهر",
|
||||
"account.settings.field.dob.year": "السنة",
|
||||
"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.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.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.gender": "الجنس",
|
||||
"account.settings.field.gender.empty": "إضافة الجنس",
|
||||
"account.settings.field.gender.options.empty": "اختر جنسًا",
|
||||
"account.settings.field.gender.options.f": "أنثى",
|
||||
"account.settings.field.gender.options.m": "ذكر",
|
||||
"account.settings.field.gender.options.o": "آخر",
|
||||
"account.settings.field.language.proficiencies": "لغة التحدّث",
|
||||
"account.settings.field.language.proficiencies.empty": "إضافة لغة التحدث",
|
||||
"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.all": "جميع المناطق الزمنية",
|
||||
"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.twitter": "تويتر",
|
||||
"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.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": "في حال التأشير، سيظهر هذا الاسم في شهاداتك و سجلاتك العامة.",
|
||||
"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.coaching.consent.welcome.header": "لنبدأ.",
|
||||
"account.settings.coaching.consent.welcome.subheader": "نحن هنا لأجلك من البداية حتى النهاية",
|
||||
"account.settings.coaching.consent.description": "تتضمن برامج MicroBachelors مرافقة تركز على مهنتك و تعليمك و كيفية تحقيقك للنتائج، و ذلك من خلال التواصل الفردي مع خبير متمرس. إن كنت مهتمًا، فيرجى تزويدنا بالمعلومات أدناه و النقر على \"إرسال\"، و سيتصل بك شريكنا في المرافقة عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
|
||||
"account.settings.coaching.consent.text-messaging.disclaimer": "* خدمات المرافقة مشمولة دون تكاليف إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. تتضمن المرافقة رسائل نصية دورية. قد تنطبق أسعار على الرسائل والبيانات. أرسل STOP للانسحاب.",
|
||||
"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": "تتضمن برامج MicroBachelors مرافقة قائمة على الرسائل النصية، تساعدك على إقران تجاربك التعلّمية مع أهدافك المهنية من خلال النصائح الفردية. خدمات المرافقة مشمولة دون تكاليف إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف جوالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل 'STOP' في أي وقت للانسحاب من الرسائل.",
|
||||
"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": "ترجى الملاحظة: إن حذف حسابك وبياناتك الشخصية له أثر دائم و لا يمكن التراجع عنه. لن يكون بمقدور {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": "نأسف لذهابك! سيُحذف حسابك في ظرف وجيز.",
|
||||
"account.settings.delete.account.modal.after.text": "حذف الحساب، بما في ذلك من إزالة من القوائم البريدية، إجراء قد يستغرق بضعة أسابيع حتى يكتمل عبر نظامنا. إن كنت تريد قبل ذلك الحين إيقاف تلقي البريد الإلكتروني، فيرجى إلغاء الاشتراك من تذييل أي بريد إلكتروني.",
|
||||
"account.settings.delete.account.modal.after.button": "إغلاق ",
|
||||
"account.settings.delete.account.text.3.edX": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى مثل شهادات MicroMasters. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف. {actionLink}.",
|
||||
"account.settings.delete.account.text.3": "قد تفقد كذلك إمكانية الوصول إلى الشهادات الموثقة و كذا مؤهلات البرامج الأخرى. يمكنك الاحتفاظ بنسخة عنها لديك قبل المواصلة إلى الحذف.",
|
||||
"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": "إلغاء",
|
||||
"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": "لا يمكن وصل أي حسابات في الوقت الراهن.",
|
||||
"account.page.title": "الحساب | {siteName}",
|
||||
"id.verification.access.blocked.denied": "لا يمكننا التحقق من هويتك في الوقت الراهن. إن لم تكن قد فعّلت حسابك بعد، فيرجى تفقد مجلد الرسائل غير المرغوب فيها بحثًا عن بريد التفعيل الإلكتروني من {email}.",
|
||||
"id.verification.next": "التالي",
|
||||
"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": "بطاقة تعريف بها صورة",
|
||||
"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.photo.take": "التقاط صورة ",
|
||||
"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.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.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.step4": "ضمن \"قائمة الحظر\"، ابحث عن \"edx.org\" وحدده.",
|
||||
"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\" من قائمة مواقع ويب وقم بتغيير الصلاحيات من خلال تحديد \"السماح\" في القائمة المنسدلة.",
|
||||
"id.verification.camera.access.failure.temporary.firefox": "لتمكين الوصول للكاميرا في متصفّح فايرفوكس: ",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step1": "فتح متصفّح فايرفوكس.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step2": "أدخل \"about:preferences\" في شريط عنوان الرابط.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step3": "حدد علامة التبويب \"الخصوصية والأمان\"، وانتقل إلى القسم \"الصلاحيات\".",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step4": "بجوار \"Camera\" (الكاميرا)، حدد \"Settings…\" (الإعدادات…) الزر.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step5": "في شريط البحث أدخل \"edx.org.\"",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step6": "في عمود الحالة لـ \"edx.org,\" حدد \"السماح\" من القائمة المنسدلة.",
|
||||
"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.step4": "حدد \"edx.org\" وقم بتغيير صلاحيات الكاميرا إلى \"السماح\".",
|
||||
"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.portrait.photo.title.camera": "التقط صورة لنفسك",
|
||||
"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": "إن احتجت لمساعدة في التقاط صورة لإرسالها، فاتصل بدعم {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": "التقط صورة لبطاقة تعريفك",
|
||||
"id.verification.id.photo.title.upload": "ارفع صورة لبطاقة تعريفك",
|
||||
"id.verification.id.photo.preview.alt": "معاينة صورة الهوية.",
|
||||
"id.verification.id.photo.instructions.camera": "عندما تكون بطاقتك في موضعها، استخدم زر 'التقاط صورة' أدناه لالتقاط صورتك. يرجى استخدام جواز سفر أو رخصة قيادة أو بطاقة تعريف أخرى تتضمن اسمك الكامل وصورة لوجهك.",
|
||||
"id.verification.id.photo.instructions.upload": "يرجى رفع صورة لبطاقة تعريفك. تأكد من أن البطاقة كاملة داخل الإطار و أنها مضاءة جيدًا. يجب أن يكون حجم الملف أقل من 10 ميجابايت. الأنساق المدعومة:",
|
||||
"id.verification.id.photo.instructions.upload.error.invalidFileType": "الملف الذي حددته ليس ضمن أنواع الصورة المدعومة. اختر رجاءً من بين الأنساق التالية:",
|
||||
"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.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": "بطاقة تعريفك",
|
||||
"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": "واحد أو أكثر من الملفات التي قمت برفعها في نَسقٍ غير مدعوم. رجاءً اختر مما يلي:",
|
||||
"id.verification.review.error": "{siteName} صفحة دعم",
|
||||
"id.verification.submitted.title": "التحقق من الهوية جارٍ",
|
||||
"id.verification.submitted.text": "لقد تلقينا معلوماتك و نحن الآن نتحقق من هويتك. سيتم إخطارك عند اكتمال عملية التحقق (عادةً في ظرف 5 أيام). إلى ذلكم الحين، لا يزال يمكنك الوصول لجميع محتويات المساق المتاحة.",
|
||||
"id.verification.return.dashboard": "العودة للوحة المعلومات",
|
||||
"id.verification.return.course": "العودة للمساق",
|
||||
"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": "انتقل إلى وضع الكاميرا",
|
||||
"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": "\nواجهنا خطأ فني أثناء محاولة رفع طلب التحقق من الهوية\nقد تكون هذه مشكلة مؤقتة، لذا يرجى المحاولة مجددًا بعد بضع دقائق\nإن استمرت المشكلة، فيرجى الذهاب إلى {support_link} للحصول على المساعدة.",
|
||||
"id.verification.account.name.edit": "تعديل {sr}"
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"account.settings.message.duplicate.tpa.provider": "La cuenta de {provider} seleccionada ya está vinculada con otra cuenta de edX. ",
|
||||
"account.settings.message.duplicate.tpa.provider": "La cuenta de {provider} seleccionada ya está vinculada con otra cuenta de {siteName}. ",
|
||||
"account.settings.message.managed.settings": "Los ajustes en el perfil son administrados por {managerTitle}. Contacte su administrador o {support} para obtener ayuda.",
|
||||
"account.settings.message.managed.settings.support": "soporte",
|
||||
"account.settings.page.heading": "Configuración de cuenta",
|
||||
@@ -11,18 +11,43 @@
|
||||
"account.settings.section.account.information": "Información de la cuenta",
|
||||
"account.settings.section.account.information.description": "Estas configuraciones incluyen información básica sobre tu cuenta.",
|
||||
"account.settings.section.profile.information": "Información del perfil",
|
||||
"account.settings.section.demographics.information": "Información opcional",
|
||||
"account.settings.section.site.preferences": "Preferencias del sitio",
|
||||
"account.settings.section.linked.accounts": "Cuentas vinculadas",
|
||||
"account.settings.section.linked.accounts.description": "Puedes vincular tus cuentas de redes sociales para simplificar el proceso de iniciar sesión en edX.",
|
||||
"account.settings.section.linked.accounts.description": "Puedes vincular tus cuentas de redes sociales para simplificar el proceso de iniciar sesión en {siteName}.",
|
||||
"account.settings.field.username": "Nombre de usuario",
|
||||
"account.settings.field.username.help.text": "El nombre que lo identifica en edX. No podrá cambiar el nombre de usuario.",
|
||||
"account.settings.field.username.help.text": "El nombre que lo identifica en {siteName}. No podrá cambiar el nombre de usuario.",
|
||||
"account.settings.field.full.name": "Nombre completo",
|
||||
"account.settings.field.full.name.empty": "Añade nombre",
|
||||
"account.settings.field.full.name.help.text": "El nombre que es usado para la verificación de identidad y aparece en sus certificados.",
|
||||
"account.settings.field.full.name.help.text.default": "El nombre que aparece en tu perfil público.",
|
||||
"account.settings.field.full.name.help.text.default.certificate": "Este nombre está seleccionado para aparecer en tus certificados y registros públicos.",
|
||||
"account.settings.field.name.verified": "Nombre verificado",
|
||||
"account.settings.field.name.verified.help.text.verified": "Este nombre ha sido verificado por una identificación con foto.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored": "Este nombre ha sido verificado por supervisión.",
|
||||
"account.settings.field.name.verified.help.text.verified.certificate": "Este nombre ha sido verificado por una identificación con foto y está seleccionado para aparecer en sus certificados y registros públicos.",
|
||||
"account.settings.field.name.verified.help.text.verified.proctored.certificate": "Este nombre se ha verificado mediante supervisión y se ha seleccionado para que aparezca en sus certificados y registros públicos.",
|
||||
"account.settings.field.name.verified.help.text.submitted": "La verificación ha sido enviada. Este proceso usualmente toma 48 horas o menos. El nombre verificado no puede ser modificado en este momento. ",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored": "Su examen supervisado ha sido enviado. El nombre verificado no se puede cambiar en este momento. Vuelva a consultar en 2-5 días.",
|
||||
"account.settings.field.name.verified.help.text.submitted.certificate": "Cuando el proceso de verificación de identidad sea exitoso, este nombre aparecerá en tus certificados y registros públicos. El nombre verificado no puede ser cambiado en este momento. ",
|
||||
"account.settings.field.name.verified.help.text.submitted.proctored.certificate": "Una vez que su examen supervisado pase la revisión, este nombre aparecerá en su certificado y en los registros públicos. El nombre verificado no se puede cambiar en este momento.",
|
||||
"account.settings.field.name.verified.verification.help": "Ingrese su nombre tal como aparece en su tarjeta de identificación vigente de estudiante, trabajo o emitida por el gobierno.",
|
||||
"account.settings.field.full.name.help.text.submitted": "La verificación ha sido enviada. Este proceso usualmente toma 48 horas o menos. El nombre verificado no puede ser modificado en este momento. ",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored": "Su examen supervisado ha sido enviado. El nombre completo no se puede cambiar en este momento. Vuelva a consultar en 2-5 días.",
|
||||
"account.settings.field.full.name.help.text.submitted.certificate": "En cuanto el proceso de verificación de identidad sea exitoso, este nombre aparecerá en tus certificados y registros públicos. El nombre completo no puede ser cambiado en este momento. ",
|
||||
"account.settings.field.full.name.help.text.submitted.proctored.certificate": "Una vez que su examen supervisado pase la revisión, este nombre aparecerá en sus certificados y registros públicos. El nombre completo no se puede cambiar en este momento.",
|
||||
"account.settings.field.name.verified.success.message": "Tu solicitud de verificación de identidad se ha completado exitosamente. Ahora puedes seleccionar el nombre prefieres que aparezca en tus certificados y registros públicos.",
|
||||
"account.settings.field.name.verified.success.message.header": "¡Tu solicitud de cambio de nombre está completada!",
|
||||
"account.settings.field.name.verified.failure.message": "Tu más reciente intento de verificación de ID no fue aprobado. Las configuraciones relacionadas con el proceso se han restablecido.",
|
||||
"account.settings.field.name.verified.failure.message.header": "No pudimos verificar tu identidad.",
|
||||
"account.settings.field.name.verified.failure.message.help.link": "Más información sobre la verificación de ID",
|
||||
"account.settings.field.name.verified.submitted.message": "Tu solicitud de verificación de identidad ha sido enviada y usualmente se tarda entre 24 a 48 horas para completarse. ",
|
||||
"account.settings.field.name.verified.submitted.message.certificate": "Cuando la solicitud esté aprobada, la actualización de tu nombre aparecerá en todos los certificados asociados y registros públicos.",
|
||||
"account.settings.field.name.verified.submitted.message.header": "¡Tu solicitud de cambio de nombre esta por completarse!",
|
||||
"account.settings.field.email": "Correo electrónico (Ingresar)",
|
||||
"account.settings.field.email.empty": "Agregar correo electrónico",
|
||||
"account.settings.field.email.confirmation": "Le enviamos un mensaje de confirmación a {value}. Hacer click en la liga del mensaje para actualizar su correo electrónico.",
|
||||
"account.settings.field.email.help.text": "Recibes mensajes de edX y equipos del curso en esta dirección.",
|
||||
"account.settings.field.email.help.text": "Tienes un mensaje de {siteName} y el equipo de curso en esta dirección. ",
|
||||
"account.settings.field.secondary.email": "Correo electrónico de recuperación",
|
||||
"account.settings.field.secondary.email.empty": "Agregar un correo electrónico de recuperación",
|
||||
"account.settings.field.secondary.email.confirmation": "Le enviamos un mensaje de confirmación a {value}. Hacer click en la liga del mensaje para actualizar su correo electrónico.",
|
||||
@@ -30,12 +55,20 @@
|
||||
"account.settings.field.dob": "Año de nacimiento",
|
||||
"account.settings.field.dob.empty": "Agregar año de nacimiento",
|
||||
"account.settings.field.year_of_birth.options.empty": "Selecciona año de nacimiento",
|
||||
"account.settings.field.dob.month": "Mes",
|
||||
"account.settings.field.dob.year": "Año",
|
||||
"account.settings.field.dob.form.button": "Por favor confirme su fecha de nacimiento",
|
||||
"account.settings.field.dob.form.title": "Confirma tu fecha de nacimiento",
|
||||
"account.settings.field.dob.form.help.text": "Solicitamos información sobre la fecha de nacimiento para ayudarnos a cumplir con nuestras obligaciones legales.",
|
||||
"account.settings.field.dob.form.success": "Gracias por ingresar la información de su fecha de nacimiento.",
|
||||
"account.settings.field.month_of_birth.options.empty": "Seleccione un mes de nacimiento",
|
||||
"account.settingsfield.dob.error.general": "Ha ocurrido un error técnico. Por favor intentalo de nuevo.",
|
||||
"account.settings.field.country": "País",
|
||||
"account.settings.field.country.empty": "Agregar país",
|
||||
"account.settings.field.country.options.empty": "Seleccionar un país",
|
||||
"account.settings.field.state": "Estado",
|
||||
"account.settings.field.state.empty": "Add state",
|
||||
"account.settings.field.state.options.empty": "Select a State",
|
||||
"account.settings.field.state.empty": "Añada un estado",
|
||||
"account.settings.field.state.options.empty": "Seleccionar un estado",
|
||||
"account.settings.field.site.language": "Idioma del sitio",
|
||||
"account.settings.field.site.language.help.text": "El idioma que se usará para el sitio. Actualmente solo hay disponibilidad de usar un número limitado de idiomas.",
|
||||
"account.settings.field.education": "Educación",
|
||||
@@ -56,7 +89,7 @@
|
||||
"account.settings.field.gender.options.f": "Femenino",
|
||||
"account.settings.field.gender.options.m": "Masculino",
|
||||
"account.settings.field.gender.options.o": "Otro",
|
||||
"account.settings.field.language.proficiencies": "Idiomas hablados",
|
||||
"account.settings.field.language.proficiencies": "Idioma hablado",
|
||||
"account.settings.field.language.proficiencies.empty": "Agregar un idioma hablado",
|
||||
"account.settings.field.language_proficiencies.options.empty": "Selecciona lenguaje",
|
||||
"account.settings.field.time.zone": "Zona horaria",
|
||||
@@ -66,7 +99,7 @@
|
||||
"account.settings.field.time.zone.all": "Todas las zonas horarias",
|
||||
"account.settings.field.time.zone.country": "Zonas horarias",
|
||||
"account.settings.section.social.media": "Enlaces de redes sociales",
|
||||
"account.settings.section.social.media.description": "Opcionalmente, conecte sus cuentas personales a los iconos de redes sociales en su perfil de edX.",
|
||||
"account.settings.section.social.media.description": "Opcionalmente, conecte sus cuentas personales a los iconos de redes sociales en su perfil de {siteName}.",
|
||||
"account.settings.field.social.platform.name.linkedin": "LinkedIn",
|
||||
"account.settings.field.social.platform.name.linkedin.empty": "Agregar perfil de LinkedIn",
|
||||
"account.settings.jump.nav.delete.account": "Eliminar mi cuenta",
|
||||
@@ -79,57 +112,248 @@
|
||||
"account.settings.editable.field.action.edit": "Editar",
|
||||
"account.settings.static.field.empty": "No hay valor establecido. Contacte su administrador {enterprise} para hacer cambios.",
|
||||
"account.settings.static.field.empty.no.admin": "No hay valor establecido.",
|
||||
"account.settings.coaching.consent.welcome.header": "Let’s get started.",
|
||||
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
|
||||
"account.settings.coaching.consent.description": "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.*",
|
||||
"account.settings.coaching.consent.text-messaging.disclaimer": "* 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.",
|
||||
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
|
||||
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
|
||||
"account.settings.coaching.consent.label.name": "Please confirm your name",
|
||||
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
|
||||
"account.settings.coaching.consent.success.header": "Success!",
|
||||
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You will receive a text message confirmation.",
|
||||
"account.settings.coaching.consent.success.continue": "Start my course",
|
||||
"account.settings.field.name.certificate.select": "En caso de ser seleccionado, este nombre aparecerá en tus certificados y registros públicos. ",
|
||||
"account.settings.field.name.modal.certificate.title": "Escoge un nombre de preferencia para tus certificados y registros públicos.",
|
||||
"account.settings.field.name.modal.certificate.select": "Selecciona un nombre",
|
||||
"account.settings.field.name.modal.certificate.option.full": "Nombre completo",
|
||||
"account.settings.field.name.modal.certificate.option.verified": "Nombre verificado",
|
||||
"account.settings.field.name.modal.certificate.button.choose": "Escoge un nombre",
|
||||
"account.settings.coaching.consent.welcome.header": "Empecemos",
|
||||
"account.settings.coaching.consent.welcome.subheader": "Estamos aquí para ustede desde el inicio hasta el final",
|
||||
"account.settings.coaching.consent.description": "Los programas de MicroBachelors incluyen entrenamiento que se enfoca en su carrera, educación y cómo logrará resultados a través de la comunicación individual con un profesional experimentado. Si está interesado, proporcione la información a continuación y haga clic en \"Enviar\", y nuestro socio asesor se comunicará con usted por correo electrónico y / o mensaje de texto para ayudarlo a avanzar. Los términos y Condiciones aplican.*",
|
||||
"account.settings.coaching.consent.text-messaging.disclaimer": "* Los servicios de entrenamiento se incluyen sin costo adicional para los alumnos con números de teléfono de EE. UU. El entrenamiento incluye mensajes de texto recurrentes. Se pueden aplicar tarifas por mensajes y datos. Envía STOP para cancelar la suscripción.",
|
||||
"account.settings.coaching.consent.accept-coaching": "Registrarse para coaching",
|
||||
"account.settings.coaching.consent.decline-coaching": "Prefiero no ser contactado con servicios de coaching gratuitos.",
|
||||
"account.settings.coaching.consent.label.name": "Por favor confirme su nombre",
|
||||
"account.settings.coaching.consent.label.phone-number": "Ingrese su número de teléfono móvil",
|
||||
"account.settings.coaching.consent.success.header": "¡Éxito!",
|
||||
"account.settings.coaching.consent.success.message": "Estás inscrito para coaching. Puedes esperar un mensaje por correo electrónico o SMS en los próximos días.",
|
||||
"account.settings.coaching.consent.success.continue": "Iniciar mi curso",
|
||||
"account.settings.coaching.managed.support": "soporte",
|
||||
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
|
||||
"account.settings.coaching.managed.alert": "{ManagerTitle} administra su Nombre. Póngase en contacto con su administrador para obtener ayuda.",
|
||||
"account.settings.field.phone_number": "Teléfono",
|
||||
"account.settings.field.phone_number.empty": "Add a phone number",
|
||||
"account.settings.field.coaching_consent": "Coaching consent",
|
||||
"account.settings.field.coaching_consent.tooltip": "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.",
|
||||
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
|
||||
"account.settings.field.phone_number.empty": "Añadir un número de teléfono",
|
||||
"account.settings.field.coaching_consent": "Consentimiento de coaching",
|
||||
"account.settings.field.coaching_consent.tooltip": "Los programas de MicroBachelors incluyen entrenamiento basado en mensajes de texto que lo ayuda a emparejar experiencias educativas con sus objetivos profesionales a través de asesoramiento personalizado. Los servicios de entrenamiento se incluyen sin costo adicional y están disponibles para estudiantes con números de teléfono móvil de EE. UU. Se aplican tarifas de mensajería estándar. Envíe \"STOP\" en cualquier momento para cancelar la suscripción a los mensajes.",
|
||||
"account.settings.field.coaching_consent.error": "Se requiere un número de teléfono válido de EE. UU. Para optar por el coaching",
|
||||
"account.settings.delete.account.before.proceeding": "Antes de continuar, por favor {actionLink}.",
|
||||
"account.settings.delete.account.header": "Eliminar mi cuenta",
|
||||
"account.settings.delete.account.subheader": "¡Sentimos que te vayas!",
|
||||
"account.settings.delete.account.text.1": "Cuidado: la eliminación de tu cuenta y datos personales es permanente e irreversible. edX no podrá recuperar ni tu cuenta ni los datos eliminados.",
|
||||
"account.settings.delete.account.text.2": "Una vez su cuenta haya sido eliminada, no la podrá usar para tomar cursos en la app de edX, edx.org o en cualquier otro sitio administrado por edX. Esto incluye el acceso a edx.org desde el sistema de su empleador o universidad y el acceso a páginas privadas ofrecidas por MIT Open Learning, Wharton Executive Education y Harvard Medical School.",
|
||||
"account.settings.delete.account.text.3.link": "siga las instrucciones para imprimir o descargar el certificado",
|
||||
"account.settings.delete.account.text.warning": "Warning: La eliminación de la cuenta es permanente. Por favor lee la información de más arriba con atención antes de proceder. Esta es una acción irreversible, y no podrás volver a usar el mismo correo electrónico en edX.",
|
||||
"account.settings.delete.account.text.1": "Para tener en cuenta: La eliminación de su cuenta y sus datos personales es permanente y no se puede deshacer. {siteName} no podrá recuperar tu cuenta o la información que sea eliminada.",
|
||||
"account.settings.delete.account.text.2": "Una véz tu cuenta haya sido eliminada, no podrás usarla para tomar cursos en {siteName}. ",
|
||||
"account.settings.delete.account.text.2.edX": "Una vez su cuenta haya sido eliminada, no la podrá usar para tomar cursos en la app de edX, edx.org o en cualquier otro sitio administrado por edX. Esto incluye el acceso a edx.org desde el sistema de su empleador o universidad y el acceso a páginas privadas ofrecidas por MIT Open Learning, Wharton Executive Education y Harvard Medical School.",
|
||||
"account.settings.delete.account.text.3.link": "Sigue estas instrucciones para imprimir o descargar un certificado. ",
|
||||
"account.settings.delete.account.text.warning": "Atención: la eliminación de su cuenta es permanente. Por favor lea el previo aviso cautelosamente antes de proceder, ya que esto es una acción irreversible, y no podrá usar el mismo correo electrónico en {siteName}.",
|
||||
"account.settings.delete.account.text.change.instead": "En lugar de eso, ¿quieres cambiar tu correo electrónico, nombre o contraseña?",
|
||||
"account.settings.delete.account.button": "Eliminar mi cuenta",
|
||||
"account.settings.delete.account.please.activate": "activar su cuenta",
|
||||
"account.settings.delete.account.please.confirm": "Confirma tu cuenta",
|
||||
"account.settings.delete.account.please.unlink": "Desvincular todas las cuentas de redes sociales.",
|
||||
"account.settings.delete.account.modal.header": "¿Está seguro?",
|
||||
"account.settings.delete.account.modal.text.1": "Has seleccionado “Eliminar mi cuenta”. La eliminación de tu cuenta y datos personales es permanente e irreversible. edX no será capaz de recuperar tu cuenta o los datos que se hayan borrado.",
|
||||
"account.settings.delete.account.modal.text.2": "Si procedes, no será posible usar esta cuenta para tomar cursos ni en la aplicación móvil de edX, ni en edx.org, ni en cualquier otro sitio hospedado por edX. Esto incluye el acceso a edx.org desde el sistema de tu empleador o universidad, y el acceso a sitios privados ofrecidos por MIT Open Learning, Wharton Executive Education, y Harvard Medical School.",
|
||||
"account.settings.delete.account.modal.text.1": "Has seleccionado la opción ''Eliminar mi cuenta''. Ten en cuenta que la eliminación de tu cuenta e información personal es permanente y no puede revertirse. {siteName} no será capaz de recuperar tu cuenta o información una vez esta sea eliminada. ",
|
||||
"account.settings.delete.account.modal.text.2": "Si aceptas proceder, ya no podrás usar esta cuenta para tomar cursos en {siteName}.",
|
||||
"account.settings.delete.account.modal.text.2.edX": "Si procedes, no será posible usar esta cuenta para tomar cursos ni en la aplicación móvil de edX, ni en edx.org, ni en cualquier otro sitio hospedado por edX. Esto incluye el acceso a edx.org desde el sistema de tu empleador o universidad, y el acceso a sitios privados ofrecidos por MIT Open Learning, Wharton Executive Education, y Harvard Medical School.",
|
||||
"account.settings.delete.account.modal.enter.password": "Si deseas continuar y eliminar tu cuenta, por favor introduce la contraseña de tu cuenta:",
|
||||
"account.settings.delete.account.modal.confirm.delete": "Si, Eliminar",
|
||||
"account.settings.delete.account.modal.confirm.cancel": "Cancelar",
|
||||
"account.settings.delete.account.error.unable.to.delete": "No es posible eliminar esta cuenta",
|
||||
"account.settings.delete.account.error.no.password": "Se requiere una contraseña",
|
||||
"account.settings.delete.account.error.invalid.password": "Contraseña incorrecta",
|
||||
"account.settings.delete.account.error.unable.to.delete.details": "Ocurrió un error al procesar tu solicitud. Por favor, intente nuevamente más tarde.",
|
||||
"account.settings.delete.account.modal.after.header": "¡Sentimos que te vayas! Tu cuenta será eliminada en breve.",
|
||||
"account.settings.delete.account.modal.after.text": "La eliminación de cuenta, incluyendo la eliminación de las listas de correo electrónico, puede tardar unas semanas en procesarse totalmente en nuestro sistema. Si quieres renunciar a recibir correos antes de que la eliminación se haya completado, por favor date de baja mediante el enlace que aparece al final de los correos.",
|
||||
"account.settings.delete.account.modal.after.button": "Cerrar",
|
||||
"account.settings.delete.account.text.3": "Puede que también pierdas el acceso a los certificados verificados y otros certificados de programas como los de los MicroMasters. Si quieres hacer una copia de dichos certificados para tus archivos antes de proceder a la eliminación, {actionLink}.",
|
||||
"account.settings.delete.account.text.3.edX": "También podrás perder el acceso a certificados verificados y a otras credenciales del programa como los certificados MicroMasters. Puedes hacer una copia de estos registros antes de proceder con la eliminación. {actionLink}.",
|
||||
"account.settings.delete.account.text.3": "También podrás perder el acceso a certificados verificados y a otras credenciales del programa. Puedes hacer una copia de estos registros antes de proceder con la eliminación. ",
|
||||
"account.settings.message.demographics.service.issue": "Ocurrió un error al intentar recuperar o guardar la información de tu cuenta. Por favor inténtalo más tarde.",
|
||||
"account.settings.field.demographics.gender": "Identidad de género",
|
||||
"account.settings.field.demographics.gender.empty": "Añade identidad de género",
|
||||
"account.settings.field.demographics.gender.options.empty": "Selecciona una identidad de género",
|
||||
"account.settings.field.demographics.gender_description": "Descripción de identidad de género",
|
||||
"account.settings.field.demographics.gender_description.empty": "Ingresa descripción",
|
||||
"account.settings.field.demographics.ethnicity": "Identidad étnica/raza",
|
||||
"account.settings.field.demographics.ethnicity.empty": "Añade identidad étnica/raza",
|
||||
"account.settings.field.demographics.ethnicity.options.empty": "Selecciona todas las que apliquen",
|
||||
"account.settings.field.demographics.income": "Ingreso familiar",
|
||||
"account.settings.field.demographics.income.empty": "Añade ingreso familiar",
|
||||
"account.settings.field.demographics.income.options.empty": "Selecciona un rango de ingreso familiar",
|
||||
"account.settings.field.demographics.military_history": "Estatus militar en EE.UU.",
|
||||
"account.settings.field.demographics.military_history.empty": "Añade estatus militar",
|
||||
"account.settings.field.demographics.military_history.options.empty": "Selecciona estatus militar",
|
||||
"account.settings.field.demographics.learner_education_level": "Tu nivel educacional",
|
||||
"account.settings.field.demographics.learner_education_level.empty": "Añade nivel educacional",
|
||||
"account.settings.field.demographics.parent_education_level": "Nivel educacional de padres/tutores",
|
||||
"account.settings.field.demographics.parent_education_level.empty": "Añade nivel educacional",
|
||||
"account.settings.field.demographics.education_level.options.empty": "Selecciona nivel educacional",
|
||||
"account.settings.field.demographics.work_status": "Estatus laboral",
|
||||
"account.settings.field.demographics.work_status.empty": "Añade estatus laboral",
|
||||
"account.settings.field.demographics.work_status.options.empty": "Selecciona estatus laboral",
|
||||
"account.settings.field.demographics.work_status_description": "Descripción estatus laboral",
|
||||
"account.settings.field.demographics.work_status_description.empty": "Ingresa descripción",
|
||||
"account.settings.field.demographics.current_work_sector": "Área profesional actual",
|
||||
"account.settings.field.demographics.current_work_sector.empty": "Añade área profesional",
|
||||
"account.settings.field.demographics.future_work_sector": "Área profesional futura",
|
||||
"account.settings.field.demographics.future_work_sector.empty": "Añade área profesional",
|
||||
"account.settings.field.demographics.work_sector.options.empty": "Selecciona área profesional",
|
||||
"account.settings.section.demographics.why": "¿Por qué {siteName} recauda esta información?",
|
||||
"account.settings.name.change.title.id": "Este cambio de nombre requiere de verificación de identidad.",
|
||||
"account.settings.name.change.title.begin": "Antes de empezar",
|
||||
"account.settings.name.change.warning.one": "Atención: esta acción actualizará el nombre que aparece en todos los certificados obtenidos a través de esta cuenta en el pasado y en aquellos en que actualmente estás obteniendo o que adquirirás en el futuro.",
|
||||
"account.settings.name.change.warning.two": "Esta acción no podrá revertirse sin antes verificar tu identidad.",
|
||||
"account.settings.name.change.id.name.label": "Ingrese su nombre tal como aparece en su tarjeta de identificación vigente de estudiante, trabajo o emitida por el gobierno.",
|
||||
"account.settings.name.change.id.name.placeholder": "Ingrese el nombre en su identificación con foto",
|
||||
"account.settings.name.change.error.valid.name": "Por favor ingresa un nombre valido.",
|
||||
"account.settings.name.change.error.general": "Ha ocurrido un error técnico. Por favor intentalo de nuevo.",
|
||||
"account.settings.name.change.continue": "Continuar",
|
||||
"account.settings.name.change.cancel": "Cancelar",
|
||||
"error.notfound.message": "La página que estas buscando no está disponible o hay un error en la URL. Por favor, comprueba la URL y vuelve a intentarlo.",
|
||||
"account.settings.editable.field.password.reset.button.confirmation.support.link": "soporte técnico",
|
||||
"account.settings.editable.field.password.reset.button.confirmation": "Hemos mandado un mensaje a {email}. Haz clic en el enlace en el mensaje para restablecer tu contraseña. ¿No recibiste el mensaje? Contáctate con {technicalSupportLink}.",
|
||||
"account.settings.editable.field.password.reset.button": "Restablecer contraseña",
|
||||
"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.button.forbidden": "Su solicitud anterior está en progreso, intente nuevamente en unos momentos.",
|
||||
"account.settings.editable.field.password.reset.label": "Contraseña",
|
||||
"account.settings.sso.link.account": "Iniciar sesión con {name}",
|
||||
"account.settings.sso.account.connected": "Vinculado",
|
||||
"account.settings.sso.account.disconnect.error": "Hubo un problema al desconectar esta Cuenta. Si el problema persiste, contacte soporte.",
|
||||
"account.settings.sso.unlink.account": "Desvincular la cuenta de {accountName} ",
|
||||
"account.settings.sso.no.providers": "No se pueden vincular cuentas en este momento."
|
||||
"account.settings.sso.no.providers": "No se pueden vincular cuentas en este momento.",
|
||||
"account.page.title": "Cuenta | {siteName}",
|
||||
"id.verification.access.blocked.denied": "No podemos verificar tu identidad en este momento. Si aún no has activado tu cuenta, comprueba en tu carpeta de correo no deseado el correo de activación de {email}.",
|
||||
"id.verification.next": "Siguiente",
|
||||
"id.verification.support": "soporte",
|
||||
"id.verification.example.card.alt": "Ejemplo de un documento de identidad válido con foto y nombre completo.",
|
||||
"id.verification.requirements.title": "Requerimientos de verificación por foto",
|
||||
"id.verification.requirements.description": "Para completa la verificación de foto, necesitarás lo siguiente:",
|
||||
"id.verification.requirements.card.device.title": "Dispositivo con cámara",
|
||||
"id.verification.requirements.card.device.allow": "Permitir",
|
||||
"id.verification.requirements.card.id.title": "Tarjeta de identificación con foto",
|
||||
"id.verification.requirements.card.id.text": "Necesitas una tarjeta de identificación válida que contenga tu nombre completo y foto, similar a una licencia de conducir o pasaporte.",
|
||||
"id.verification.privacy.title": "Información de privacidad",
|
||||
"id.verification.privacy.need.photo.question": "¿Por qué {siteName} necesita mi foto?",
|
||||
"id.verification.privacy.need.photo.answer": "Utilizamos tus fotos de verificación para confirmar tu identidad y garantizar la validez de tu certificado.",
|
||||
"id.verification.privacy.do.with.photo.question": "¿Qué hace {siteName} con esta foto?",
|
||||
"id.verification.privacy.do.with.photo.answer": "Encriptamos de forma segura tu foto y la enviamos a nuestro servicio de autorización para su revisión. Tu foto e información no se guardan ni se ven en ninguna parte de {siteName} después de que se completa el proceso de verificación.",
|
||||
"id.verification.access.blocked.title": "Verificación de identidad",
|
||||
"id.verification.access.blocked.enrollment": "Actualmente, no estás inscrito en un curso que requiera verificación de identidad.",
|
||||
"id.verification.access.blocked.pending": "Ya has enviado tu información de verificación de identidad. Recibirás un mensaje en tu panel principal cuando el proceso de verificación esté completado (usualmente dentro de los 5 días).",
|
||||
"id.verification.photo.take": "Tomar la foto",
|
||||
"id.verification.photo.retake": "¿Tomar nuevamente la foto?",
|
||||
"id.verification.photo.enable.detection": "Habilitar la detección de rostro",
|
||||
"id.verification.photo.enable.detection.portrait.help.text": "Si está marcada, aparecerá un cuadro alrededor de tu cara. Tu rostro se puede ver claramente si el cuadro que lo rodea es azul. Si Tu cara no está en una buena posición o es indetectable, el cuadro será rojo.",
|
||||
"id.verification.photo.enable.detection.id.help.text": "Si está marcada, aparecerá una casilla alrededor de la cara de tu documento de identificación. La cara se puede ver claramente si la caja que la rodea es azul. Si la cara no está en una buena posición o es indetectable, el cuadro será rojo.",
|
||||
"id.verification.photo.feedback.correct": "La cara está en una buena posición.",
|
||||
"id.verification.photo.feedback.two.faces": "Más de un rostro detectado.",
|
||||
"id.verification.photo.feedback.no.faces": "No se detectó el rostro.",
|
||||
"id.verification.photo.feedback.top.left": "Posición incorrecta. Arriba a la izquierda.",
|
||||
"id.verification.photo.feedback.top.center": "Posición incorrecta. Centro Superior.",
|
||||
"id.verification.photo.feedback.top.right": "Posición incorrecta. Parte superior derecha.",
|
||||
"id.verification.photo.feedback.center.left": "Posición incorrecta. Centro izquierda.",
|
||||
"id.verification.photo.feedback.center.center": "Posición incorrecta. Demasiado cerca de la cámara.",
|
||||
"id.verification.photo.feedback.center.right": "Posición incorrecta. Centro derecha.",
|
||||
"id.verification.photo.feedback.bottom.left": "Posición incorrecta. Abajo a la izquierda.",
|
||||
"id.verification.photo.feedback.bottom.center": "Posición incorrecta. Parte inferior central.",
|
||||
"id.verification.photo.feedback.bottom.right": "Posición incorrecta. Abajo a la derecha.",
|
||||
"id.verification.camera.access.title": "Permisos de la cámara",
|
||||
"id.verification.camera.access.title.success": "Acceso a la cámara habilitado",
|
||||
"id.verification.camera.access.title.failed": "Acceso a la cámara no habilitado",
|
||||
"id.verification.camera.access.click.allow": "Por favor asegúrate de hacer clic en \"Permitir\"",
|
||||
"id.verification.camera.access.enable": "Habilitar cámara",
|
||||
"id.verification.camera.access.problems": "¿Tienes problemas?",
|
||||
"id.verification.camera.access.skip": "Omitir y cargar un archivo de imagen",
|
||||
"id.verification.camera.access.success": "Parece que tu cámara está funcionando y está lista.",
|
||||
"id.verification.camera.access.failure": "Parece que no podemos acceder a tu cámara. Deberás cargar archivos de imagen de tu rostro y tu identificación con foto.",
|
||||
"id.verification.camera.access.failure.temporary": "Parece que no podemos acceder a tu cámara. Verifica que tu cámara web esté conectada y que permitiste que tu navegador acceda a ella.",
|
||||
"id.verification.camera.access.failure.temporary.chrome": "Para habilitar el acceso a la cámara en Chrome:",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step1": "Abrir Chrome.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2": "Navega a Más > Configuración.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.windows": "Para Windows: Alt + F, Alt + E o F10 seguido de la barra espaciadora",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step2.mac": "Para Mac: Comando +,",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step3": "En la pestaña \"Privacidad y seguridad\", selecciona \"Configuración del sitio\" y luego \"Cámara.\"",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step4": "En \"Bloqueado\", busca \"edx.org\" y selecciónalo.",
|
||||
"id.verification.camera.access.failure.temporary.chrome.step5": "En la sección \"Permisos\", actualiza los permisos de la cámara a \"Permitir\".",
|
||||
"id.verification.camera.access.failure.temporary.ie11": "Para habilitar el acceso a la cámara en Internet Explorer:",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step1": "Abre el Administrador de configuración de Flash Player navegando a Configuración de Windows > Panel de control > Flash Player.",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step2": "Selecciona la pestaña \"Cámara y micrófono,\" y luego selecciona el botón \"Configuración de cámara y micrófono por sitio.\"",
|
||||
"id.verification.camera.access.failure.temporary.ie11.step3": "Elige \"edx.org\" de la lista de sitios web y cambia los permisos seleccionando \"Permitir\" en el menú desplegable.",
|
||||
"id.verification.camera.access.failure.temporary.firefox": "Para habilitar el acceso a la cámara en Firefox:",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step1": "Abrir Firefox.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step2": "Coloca \"about:preferences\" en la barra del URL.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step3": "Selecciona la pestaña \"Privacidad y seguridad\" y navega hasta la sección \"Permisos.\"",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step4": "Junto a \"Cámara,\" selecciona el botón \"Configuración...\"",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step5": "En la barra de búsqueda, ingresa \"edx.org\".",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step6": "En la columna de estado de \"edx.org\", selecciona \"Permitir\" en el menú desplegable.",
|
||||
"id.verification.camera.access.failure.temporary.firefox.step7": "Selecciona \"Guardar cambios.\"",
|
||||
"id.verification.camera.access.failure.temporary.safari": "Para habilitar el acceso a la cámara en Safari:",
|
||||
"id.verification.camera.access.failure.temporary.safari.step1": "Abrir Safari.",
|
||||
"id.verification.camera.access.failure.temporary.safari.step2": "Haz clic en el menú de la aplicación Safari y luego selecciona \"Preferencias\". También puedes utilizar Command +, como método abreviado de teclado.",
|
||||
"id.verification.camera.access.failure.temporary.safari.step3": "Selecciona la pestaña \"Sitios web\" y luego selecciona \"Cámara\".",
|
||||
"id.verification.camera.access.failure.temporary.safari.step4": "Selecciona \"edx.org\" y cambia los permisos de la cámara a \"Permitir.\"",
|
||||
"id.verification.camera.access.failure.unsupported": "Parece que tu navegar no soporta el acceso a la cámara.",
|
||||
"id.verification.camera.access.failure.unsupported.chrome.explanation": "El navegador de Chrome actualmente no soporta el acceso a la cámara en dispositivos iOS, como iPhones y iPads.",
|
||||
"id.verification.camera.access.failure.unsupported.instructions": "Por favor utilice otro navegador para completar la verificación de identidad.",
|
||||
"id.verification.photo.tips.title": "Consejos útiles de fotos",
|
||||
"id.verification.photo.tips.description": "A continuación, necesitaremos que tomes una foto de tu rostro. Por favor, revisa los siguientes consejos útiles.",
|
||||
"id.verification.photo.tips.list.title": "Consejos para fotos",
|
||||
"id.verification.photo.tips.list.description": "Para tomar la foto correctamente, asegúrate de: ",
|
||||
"id.verification.photo.tips.list.well.lit": "El rostro esté bien iluminado",
|
||||
"id.verification.photo.tips.list.inside.frame": "Tu cara está completamente dentro del marco de la foto.",
|
||||
"id.verification.portrait.photo.title.camera": "Toma una foto de ti mismo",
|
||||
"id.verification.portrait.photo.instructions.camera": "Cuando tu rostro esté en posición, usa el botón Tomar foto a continuación para tomar tu foto.",
|
||||
"id.verification.camera.help.sight.question": "¿Qué pasa si no puedo ver la imagen de la cámara o si no puedo ver mi foto para determinar qué lado es visible?",
|
||||
"id.verification.camera.help.sight.answer.portrait": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. La posición óptima de la cámara varía con cada computadora, pero generalmente la mejor distancia para una foto de rostro es aproximadamente a 12 a 18 pulgadas (30 a 45 centímetros) de la cámara, con la cabeza centrada en relación con la pantalla de la computadora. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación.",
|
||||
"id.verification.camera.help.sight.answer.id": "Es posible que puedas completar el procedimiento de captura de imágenes sin ayuda, pero es posible que necesites un par de intentos de envío para que la cámara se coloque correctamente. El posicionamiento óptimo de la cámara varía con cada computadora pero, generalmente, la mejor distancia para una foto de un documento de identificación es a 8 a 12 pulgadas (20 a 30 centímetros) de la cámara, con el documento de identificación centrado en relación con la cámara. Si las fotos que envías son rechazadas, intenta mover la computadora o la orientación de la cámara para cambiar el ángulo de iluminación. La razón más común de rechazo es la imposibilidad de leer el texto del documento de identidad.",
|
||||
"id.verification.camera.help.difficulty.question.portrait": "¿Qué sucede si tengo dificultades para mantener la cabeza en posición con respecto a la cámara?",
|
||||
"id.verification.camera.help.difficulty.question.id": "¿Qué sucede si tengo dificultades para mantener mi identificación en posición con respecto a la cámara?",
|
||||
"id.verification.camera.help.difficulty.answer": "Si requieres de asistencia para cargar una foto, contacta el equipo de soporte de {siteName} para sugerencias adicionales.",
|
||||
"id.verification.id.photo.unclear.question": "¿No es clara la foto de tu tarjeta de ID ó es muy borrosa?",
|
||||
"id.verification.id.tips.title": "Tips de ayuda para tarjetas de identificación",
|
||||
"id.verification.id.tips.description": "Después, necesitaremos que tomes una foto de una tarjeta de ID válida que tengas en la que incluya tu nombre completo y foto, como una licencia de conducción o un pasaporte. Por favor ten tú ID lista.",
|
||||
"id.verification.id.tips.list.well.lit": "Tu tarjeta de identificación está bien iluminada.",
|
||||
"id.verification.id.tips.list.clear": "Asegúrate de que puedes ver tu foto y leer claramente tu nombre.",
|
||||
"id.verification.id.photo.title.camera": "Toma una foto de tu tarjeta de identificación",
|
||||
"id.verification.id.photo.title.upload": "Sube una foto de tu tarjeta de identificación",
|
||||
"id.verification.id.photo.preview.alt": "Previsualización de Foto ID",
|
||||
"id.verification.id.photo.instructions.camera": "Cuando tú ID este en posición, utiliza el botón de tomar foto que se encuentra debajo para proceder a tomar tu foto. Por favor usa un pasaporte, licencia de conducción, u otra tarjeta de identificación que incluya tu nombre completo y una foto de tu rostro.",
|
||||
"id.verification.id.photo.instructions.upload": "Por favor sube una foto de tu identificación. Asegurate de que todo el ID encaje dentro de el marco y que este bien iluminado. El tamaño del archivo debe ser por debajo de 10 MB. Los formatos soportados son: ",
|
||||
"id.verification.id.photo.instructions.upload.error.invalidFileType": "El archivo que has seleccionado no cumple con el tipo de imágenes soportadas. Por favor escoge entre los siguientes formatos:",
|
||||
"id.verification.id.photo.instructions.upload.error.fileTooLarge": "El archivo que has seleccionado es demasiado grande. Vuelve a intentarlo con un archivo de menos de 10 MB.",
|
||||
"id.verification.name.check.title": "Revisa nuevamente tu nombre",
|
||||
"id.verification.name.check.instructions": "¿El nombre a continuación coincide con el nombre en su identificación con foto? De lo contrario, actualice el nombre a continuación para que coincida con su identificación con foto.",
|
||||
"id.verification.name.check.mismatch.information": "Si el nombre a continuación no coincide con su identificación con foto, se denegará su verificación de identidad.",
|
||||
"id.verification.name.error": "Ingrese su nombre tal como aparece en su identificación con foto.",
|
||||
"id.verification.account.name.warning.prefix": "Ten en cuenta:",
|
||||
"id.verification.account.name.settings": "Configuración de cuenta",
|
||||
"id.verification.name.label": "Nombre",
|
||||
"id.verification.account.name.photo.alt": "Foto de tu identificación a enviar.",
|
||||
"id.verification.review.title": "Revisar tus fotos",
|
||||
"id.verification.review.description": "Asegúrate de que podamos verificar tu identidad con las imágenes y la información suministradas.",
|
||||
"id.verification.review.portrait.label": "Tu Retrato",
|
||||
"id.verification.review.portrait.alt": "Foto de tu rostro a enviar.",
|
||||
"id.verification.review.portrait.retake": "Retomar Foto de Retrato",
|
||||
"id.verification.review.id.label": "Tú tarjeta de identificación",
|
||||
"id.verification.review.id.alt": "Foto de tu tarjeta de identificación para ser enviada.",
|
||||
"id.verification.review.id.retake": "Retomar Foto de identificación",
|
||||
"id.verification.review.confirm": "Enviar",
|
||||
"id.verification.submission.alert.error.face": "Se requiere una foto de tu rostro. Vuelve a tomar tu foto de retrato.",
|
||||
"id.verification.submission.alert.error.id": "Se requiere una foto de tu documento de ID. Vuelve a tomar tu foto de ID.",
|
||||
"id.verification.submission.alert.error.name": "Se requiere un nombre de cuenta válido. Actualiza el nombre de tu cuenta para que coincida con el nombre que figura en tu ID.",
|
||||
"id.verification.submission.alert.error.unsupported": "Uno o más de los siguientes archivos que has subido se encuentran en un formato no soportado. Por favor escoge de los siguientes formatos:",
|
||||
"id.verification.review.error": "Página de soporte de {siteName}",
|
||||
"id.verification.submitted.title": "Verificación de identidad en progreso.",
|
||||
"id.verification.submitted.text": "Hemos recibido su información y estamos verificando su identidad. Se le notificará cuando se complete el proceso de verificación (generalmente dentro de los 5 días). Mientras tanto, aún puede acceder a todo el contenido del curso disponible.",
|
||||
"id.verification.return.dashboard": "Volver al panel principal",
|
||||
"id.verification.return.course": "Regresar al curso",
|
||||
"id.verification.return.generic": "Regresar",
|
||||
"id.verification.photo.upload.help.title": "Cargar una imágen en vez",
|
||||
"id.verification.photo.camera.help.title": "Usar la cámara en vez",
|
||||
"id.verification.photo.upload.help.text": "Si estás presentando problemas usando el capturador de fotos, es posible que prefieras subir una foto en vez. Para subir una foto, has clic en el botón a continuación ",
|
||||
"id.verification.photo.camera.help.text": "Si estás presentando problemas subiendo una foto, es posible que prefieras usar la cámara en vez. Para usar una cámara, has clic en el botón a continuación.",
|
||||
"id.verification.upload.help.button": "Cambia a modo de carga",
|
||||
"id.verification.camera.help.button": "Cambia a modo de cámara",
|
||||
"id.verification.request.camera.access.instructions": "Para tomar una foto con tu cámara web, es posible que recibas un aviso del navegador para acceder a tu cámara. {clickAllow}",
|
||||
"id.verification.requirements.account.managed.alert": "La configuración de tu perfil es administrada por {managerTitle}. Por lo tanto si el nombre en tu ID de foto coincide con tu nombre de cuenta por favor ponte en contacto con tu administrador de {profileDataManager} o con {soporte} para solicitar ayuda antes de completar el proceso de verificación de foto.",
|
||||
"id.verification.requirements.card.device.text": "Necesitas un dispositivo que tenga una cámara. Si has recibido un aviso del navegador para habilitar acceso a tu cámara, por favor asegúrate de seleccionar [allow].",
|
||||
"id.verification.account.name.summary.alert": "La configuración de tu perfil es administrada por {managerTitle}. Por lo tanto si el nombre en tu ID de foto coincide con tu nombre de cuenta por favor ponte en contacto con tu administrador de {profileDataManager} o con {soporte} para solicitar ayuda.",
|
||||
"idv.submission.alert.error": "\n Se produjo un error técnico al intentar enviar la verificación de ID.\n Es posible que sea una cuestión temporal, así que inténtalo de nuevo en unos minutos.\n Si el problema continúa, dirígete a {support_link} para obtener ayuda.\n ",
|
||||
"id.verification.account.name.edit": "Editar {sr}"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user