Compare commits
625 Commits
mashal-m/p
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1c35f134c | ||
|
|
41af76f027 | ||
|
|
57a3b0963c | ||
|
|
92dcdd0a98 | ||
|
|
29f0cefc1e | ||
|
|
794a1d57b9 | ||
|
|
fe46e8a1a6 | ||
|
|
a525d3c22e | ||
|
|
81a2c3c0d2 | ||
|
|
0018eafdcc | ||
|
|
e7db2ef753 | ||
|
|
2e986d9b74 | ||
|
|
7c85195a27 | ||
|
|
b686acf5f5 | ||
|
|
166deeafbd | ||
|
|
f33fe7d0e5 | ||
|
|
e2896dbf94 | ||
|
|
f07d266a43 | ||
|
|
69cdc5f191 | ||
|
|
4f51f71acc | ||
|
|
c70eca1fde | ||
|
|
39dc5bbbd2 | ||
|
|
7fa61f3714 | ||
|
|
8ce7c1599d | ||
|
|
03bdcff331 | ||
|
|
d73d840e93 | ||
|
|
d23b5f53df | ||
|
|
da98bfa021 | ||
|
|
c37640aa69 | ||
|
|
ea35227389 | ||
|
|
c35bf95c1c | ||
|
|
bd26928154 | ||
|
|
cdc8efe17b | ||
|
|
8b6535ea58 | ||
|
|
4c9498971a | ||
|
|
a6b6a3f940 | ||
|
|
cab1a24e10 | ||
|
|
f6b7782d24 | ||
|
|
c7bbe8d0d1 | ||
|
|
20fd7ea13b | ||
|
|
d55d38ec12 | ||
|
|
e77c6ee74a | ||
|
|
8cb30bedd8 | ||
|
|
0ab3f5f669 | ||
|
|
9eaab9c2e5 | ||
|
|
ac28626b3c | ||
|
|
3f90fea26c | ||
|
|
a0466852d6 | ||
|
|
9fad507ada | ||
|
|
73351fa8e8 | ||
|
|
c8528a7874 | ||
|
|
124909ab74 | ||
|
|
77f66f3afb | ||
|
|
102288407f | ||
|
|
dd7c35497e | ||
|
|
8331d37b7f | ||
|
|
1d12506b01 | ||
|
|
c22c4ec5a6 | ||
|
|
69fc4be952 | ||
|
|
9d946eacd8 | ||
|
|
0af0935e86 | ||
|
|
fd33842109 | ||
|
|
13b5f3bc12 | ||
|
|
502ad904ea | ||
|
|
594ae27c0e | ||
|
|
0924cb1ba3 | ||
|
|
90ee5800b4 | ||
|
|
6be87b4a82 | ||
|
|
2137e985b3 | ||
|
|
ca93f890e1 | ||
|
|
6a57622a3c | ||
|
|
3d490c3879 | ||
|
|
1e09c83300 | ||
|
|
b660903836 | ||
|
|
3d2b8416f9 | ||
|
|
4afd07201b | ||
|
|
94ead51915 | ||
|
|
587e3f2647 | ||
|
|
3394656ed2 | ||
|
|
66eda1a58d | ||
|
|
c1ccc8c201 | ||
|
|
4ae65cfcee | ||
|
|
ad7e2035bc | ||
|
|
d5d67dbe14 | ||
|
|
3423e5efea | ||
|
|
f3d30925a8 | ||
|
|
1ec2c3b262 | ||
|
|
260df228fb | ||
|
|
6b740a89c6 | ||
|
|
03f8fdbdc3 | ||
|
|
0b86166a57 | ||
|
|
46eefa7592 | ||
|
|
036b4be854 | ||
|
|
eae0bfdca2 | ||
|
|
05f5903cbc | ||
|
|
725ae950f4 | ||
|
|
38c4f3bad3 | ||
|
|
46acf2a5a4 | ||
|
|
e8aafef127 | ||
|
|
cf451770ed | ||
|
|
d9c3975f26 | ||
|
|
5f42857332 | ||
|
|
f6babc2db9 | ||
|
|
67a053f3e0 | ||
|
|
845ab30af5 | ||
|
|
3244ecf70b | ||
|
|
a1390ebf36 | ||
|
|
89f9d9511f | ||
|
|
bc66c74a33 | ||
|
|
d760be1a53 | ||
|
|
929a34a0f6 | ||
|
|
55fc919c6f | ||
|
|
102a93486e | ||
|
|
981ba84163 | ||
|
|
8aa918bfb9 | ||
|
|
4ca2ab9f4e | ||
|
|
c01f1854ee | ||
|
|
987484d205 | ||
|
|
f86496468e | ||
|
|
e916ba29b9 | ||
|
|
6410ce1d8f | ||
|
|
d0eebfa0ea | ||
|
|
77daf2fbad | ||
|
|
354426037e | ||
|
|
b9af9ed700 | ||
|
|
2342eaae82 | ||
|
|
a1484264fb | ||
|
|
cff4a76b0c | ||
|
|
3e2e8095b4 | ||
|
|
ebd63a13a9 | ||
|
|
fd0d08daa1 | ||
|
|
6ae4c2d68b | ||
|
|
95331d1b10 | ||
|
|
7c63b66d8e | ||
|
|
810f506e52 | ||
|
|
33fd669c8f | ||
|
|
5c5204fb17 | ||
|
|
95db89a9dd | ||
|
|
81a878a658 | ||
|
|
b502de846a | ||
|
|
39f0123820 | ||
|
|
7ee70193c0 | ||
|
|
0e1574dba7 | ||
|
|
0d45ae6599 | ||
|
|
78246cf26b | ||
|
|
ca193563ec | ||
|
|
d0efd35e66 | ||
|
|
375b704eef | ||
|
|
ae121358db | ||
|
|
92b7c58af7 | ||
|
|
a4097fe6fc | ||
|
|
397f688300 | ||
|
|
8bd4b1b9a8 | ||
|
|
54d029c181 | ||
|
|
e6a4636147 | ||
|
|
efb4162926 | ||
|
|
6061232e10 | ||
|
|
ba6b8c8f9b | ||
|
|
9c16ba0075 | ||
|
|
02b987909b | ||
|
|
1bcc54bb05 | ||
|
|
5a5b0b905b | ||
|
|
44ed49c7d2 | ||
|
|
386baa3840 | ||
|
|
f1a56ad6bc | ||
|
|
465bb9f7a0 | ||
|
|
f9b7525d44 | ||
|
|
c7e82295c2 | ||
|
|
e02cf28b54 | ||
|
|
18c51e8e73 | ||
|
|
88b444e796 | ||
|
|
71635b33b6 | ||
|
|
515890d5ef | ||
|
|
c2960e1232 | ||
|
|
17e12f7f87 | ||
|
|
ffc39868e9 | ||
|
|
9ba01af816 | ||
|
|
fc222cc76c | ||
|
|
99a39568de | ||
|
|
9ce3cbfddd | ||
|
|
55a72b3f6e | ||
|
|
597004c82e | ||
|
|
3458b6f410 | ||
|
|
b70b4a796f | ||
|
|
d76945d2f2 | ||
|
|
7ee34f7d9a | ||
|
|
96c619cab8 | ||
|
|
9eda5e588c | ||
|
|
3aff0e03e0 | ||
|
|
852358f243 | ||
|
|
5ffd17db4c | ||
|
|
6f3c9616d6 | ||
|
|
5f4812ed47 | ||
|
|
d694b5c428 | ||
|
|
6b73130e9b | ||
|
|
d608f3947e | ||
|
|
db83838d5e | ||
|
|
004cdf35f1 | ||
|
|
6cb015e49d | ||
|
|
c5ae2c40d7 | ||
|
|
46edd0cb70 | ||
|
|
2c6b5c34a9 | ||
|
|
b000d2e048 | ||
|
|
437fba16fe | ||
|
|
c4038b4085 | ||
|
|
496ff21015 | ||
|
|
23e16ac6e0 | ||
|
|
d8c5762ee2 | ||
|
|
4d2d520234 | ||
|
|
b094722772 | ||
|
|
a9f061f10c | ||
|
|
4eb5d4effc | ||
|
|
925bcce81c | ||
|
|
f44e03c7c8 | ||
|
|
999988dd88 | ||
|
|
7ec1226965 | ||
|
|
5b00275371 | ||
|
|
30c1158775 | ||
|
|
c196b60b39 | ||
|
|
3022a267b1 | ||
|
|
19aacdfaf9 | ||
|
|
530cca7c59 | ||
|
|
e66d77cdd1 | ||
|
|
30333edfa2 | ||
|
|
538d85b5dd | ||
|
|
8066ae58f1 | ||
|
|
8ce7362fd8 | ||
|
|
9c6f247f0a | ||
|
|
085bc19313 | ||
|
|
ee7c769c2f | ||
|
|
a37be2dac1 | ||
|
|
d0fdc2ac5d | ||
|
|
8a4eecf6c2 | ||
|
|
e2e4003b44 | ||
|
|
434f114220 | ||
|
|
c31e1648bf | ||
|
|
379d2df2fc | ||
|
|
5933ae3034 | ||
|
|
d1269197ac | ||
|
|
12438e79f5 | ||
|
|
57317a4bb6 | ||
|
|
3bbc068a73 | ||
|
|
4179aa798a | ||
|
|
a1877bb499 | ||
|
|
cd44f15634 | ||
|
|
83242b1042 | ||
|
|
116c30663b | ||
|
|
0ed92cc7e8 | ||
|
|
3ec887089d | ||
|
|
b46165ede0 | ||
|
|
752d760030 | ||
|
|
3e2c4dc760 | ||
|
|
4136a63e92 | ||
|
|
ca8c9ef4fe | ||
|
|
14f1cfd0e1 | ||
|
|
f81ec9544a | ||
|
|
11d7d4beb4 | ||
|
|
70fc2f9642 | ||
|
|
f1d3c88f76 | ||
|
|
c5c05c86f7 | ||
|
|
b845b34b02 | ||
|
|
27c666cc27 | ||
|
|
dcdd024809 | ||
|
|
dab4b1dc92 | ||
|
|
c40bcd49ea | ||
|
|
8e3de876d0 | ||
|
|
aff083335c | ||
|
|
18ea60b470 | ||
|
|
9260b4834c | ||
|
|
d7a5b222ee | ||
|
|
17ce32b80c | ||
|
|
d4693a2c2e | ||
|
|
a446d5d3fc | ||
|
|
8f4139ad87 | ||
|
|
87e591cd71 | ||
|
|
90c02279e3 | ||
|
|
600a3de7b6 | ||
|
|
4390ca5435 | ||
|
|
5903ad2ae8 | ||
|
|
cc50ad9744 | ||
|
|
4e69301a4e | ||
|
|
3a877eb55c | ||
|
|
673639caee | ||
|
|
474f7c018d | ||
|
|
46398e7edc | ||
|
|
dfb06268ab | ||
|
|
b0301d5028 | ||
|
|
89d848e408 | ||
|
|
56ba73e455 | ||
|
|
c3d30227a3 | ||
|
|
f5bedd7708 | ||
|
|
218c796500 | ||
|
|
604845f4a3 | ||
|
|
751cdb1076 | ||
|
|
0ebc4eb3cd | ||
|
|
8a954c19e2 | ||
|
|
18b999b9a8 | ||
|
|
b155d04814 | ||
|
|
6f715e598f | ||
|
|
ab8d6e7913 | ||
|
|
d2bb164fab | ||
|
|
5aacaf44a3 | ||
|
|
4502dbe413 | ||
|
|
f5e2fa7448 | ||
|
|
5954b3122f | ||
|
|
2bf71cce10 | ||
|
|
8fd0320c88 | ||
|
|
da15a70942 | ||
|
|
52f4c51362 | ||
|
|
a0c2fb2686 | ||
|
|
223f792e2b | ||
|
|
175d9cf5cb | ||
|
|
94ef29938f | ||
|
|
4e38c9e9a6 | ||
|
|
81a1ec7e1e | ||
|
|
90da11782f | ||
|
|
82c9e07f0f | ||
|
|
16fb3a1bb4 | ||
|
|
e623f03c4b | ||
|
|
b2173afb9c | ||
|
|
c787a77f9e | ||
|
|
075587c1f0 | ||
|
|
a0b0b1f8d4 | ||
|
|
16f20cad66 | ||
|
|
40c67995a4 | ||
|
|
0fc68f7d93 | ||
|
|
90d3668128 | ||
|
|
1440c8239c | ||
|
|
bb29f9624e | ||
|
|
746b0fed4b | ||
|
|
02d14b95a7 | ||
|
|
484fc95ea0 | ||
|
|
bdb851eb8b | ||
|
|
b984296550 | ||
|
|
39fca96523 | ||
|
|
d1e817d4ba | ||
|
|
b35042ca97 | ||
|
|
88e9eb3fdf | ||
|
|
4409be4cc6 | ||
|
|
e314de2042 | ||
|
|
fc329fe9c2 | ||
|
|
aea3a7c830 | ||
|
|
35b5459dec | ||
|
|
2b82ac9faf | ||
|
|
24ad8851e6 | ||
|
|
b62645349e | ||
|
|
d2c160b771 | ||
|
|
6e39072334 | ||
|
|
245bf9aa6b | ||
|
|
b8a4163629 | ||
|
|
a5730b625a | ||
|
|
f03ac8b6d9 | ||
|
|
9e72f1c9e9 | ||
|
|
9693d938c6 | ||
|
|
f45bb43064 | ||
|
|
32f9804e4a | ||
|
|
feef40f6d0 | ||
|
|
a266e3dca9 | ||
|
|
6cc50a983e | ||
|
|
16caae04af | ||
|
|
2452d09913 | ||
|
|
b7f2a1f689 | ||
|
|
2416782eca | ||
|
|
5d0a428bdf | ||
|
|
fe4909ce07 | ||
|
|
9d61e836a5 | ||
|
|
891e28ec59 | ||
|
|
5ec4893f47 | ||
|
|
c5c0cf5392 | ||
|
|
350c2080ec | ||
|
|
44b718ad02 | ||
|
|
97333ada47 | ||
|
|
3467534bc7 | ||
|
|
5f43f945bb | ||
|
|
0ee0f41f3e | ||
|
|
35b66ae38b | ||
|
|
361e14c980 | ||
|
|
8e967fa3bf | ||
|
|
67feee5e0b | ||
|
|
837ac4e635 | ||
|
|
5c6ddd2888 | ||
|
|
7ef1b5b92d | ||
|
|
672d39f99c | ||
|
|
08a0d8e30b | ||
|
|
208c7a1ada | ||
|
|
afe2a754bd | ||
|
|
594ffe4aa9 | ||
|
|
8ecdedcdc8 | ||
|
|
11e144dec0 | ||
|
|
c71e586e64 | ||
|
|
d0d2aeed71 | ||
|
|
737833cdeb | ||
|
|
c00ebc9f64 | ||
|
|
41ee7e9538 | ||
|
|
b7181d5643 | ||
|
|
50e0235a6a | ||
|
|
5166546048 | ||
|
|
49d3acd1a1 | ||
|
|
cdf799d515 | ||
|
|
ed26467f0d | ||
|
|
fea3991915 | ||
|
|
30ca5f577e | ||
|
|
4047c3c923 | ||
|
|
3d32c8624d | ||
|
|
0c70e31655 | ||
|
|
10e1a451f7 | ||
|
|
7f2d3700d7 | ||
|
|
eb88d9b46d | ||
|
|
26dbac709f | ||
|
|
d6592a77f9 | ||
|
|
91d0feaed9 | ||
|
|
237599c830 | ||
|
|
56d0ad3db9 | ||
|
|
c0b504763e | ||
|
|
4aa44b89a3 | ||
|
|
be661b8a27 | ||
|
|
3c75052d8b | ||
|
|
229a674469 | ||
|
|
2c75c7f790 | ||
|
|
d595986d0e | ||
|
|
43d8784014 | ||
|
|
f15b71d20c | ||
|
|
aae79c45bb | ||
|
|
5233c6aa59 | ||
|
|
d59c9e38bd | ||
|
|
eb35897bf7 | ||
|
|
c28b8c6840 | ||
|
|
1698720aad | ||
|
|
57a2c7bcb6 | ||
|
|
f6f67a1a80 | ||
|
|
aec7e1281f | ||
|
|
cdf0ee5e4e | ||
|
|
f0858b7381 | ||
|
|
5f49bedaa8 | ||
|
|
a94f472459 | ||
|
|
2fc2d86a44 | ||
|
|
899f88ca1e | ||
|
|
399dd04df6 | ||
|
|
46dd8739ab | ||
|
|
5de18e0dba | ||
|
|
ca3bc9151c | ||
|
|
9cccf09394 | ||
|
|
ced29278d5 | ||
|
|
bc8197a9af | ||
|
|
f86677171f | ||
|
|
966b59b70f | ||
|
|
0e6aff7c6f | ||
|
|
13dbb732e3 | ||
|
|
b80748aa4c | ||
|
|
f098fe1a3a | ||
|
|
a989fabb92 | ||
|
|
1257e81781 | ||
|
|
b08890b794 | ||
|
|
7f1c7b86ef | ||
|
|
a5fd3a7f7e | ||
|
|
4513cc8834 | ||
|
|
40103a2386 | ||
|
|
b6c18bb439 | ||
|
|
ee51939f2d | ||
|
|
fc127ccd98 | ||
|
|
22faebac50 | ||
|
|
5ce3995f5b | ||
|
|
e181269703 | ||
|
|
5d212ec6b5 | ||
|
|
363096c4f0 | ||
|
|
85c5902559 | ||
|
|
fca6da2df7 | ||
|
|
7a74e5d29b | ||
|
|
4b94344dd9 | ||
|
|
5245254de5 | ||
|
|
afda76ff11 | ||
|
|
da52ddd35a | ||
|
|
bb2aef3878 | ||
|
|
4d6f76d9b3 | ||
|
|
869e083a2a | ||
|
|
ee876b5c84 | ||
|
|
a2b25449de | ||
|
|
16218252f1 | ||
|
|
757e446be7 | ||
|
|
db0f8f80bc | ||
|
|
49bc817f2d | ||
|
|
c31beeef96 | ||
|
|
09970d7935 | ||
|
|
d15b0baf74 | ||
|
|
b9802a130e | ||
|
|
7c0ea75e21 | ||
|
|
e1b02de7de | ||
|
|
6c4dbc5db0 | ||
|
|
d180626122 | ||
|
|
a9518b7388 | ||
|
|
51b18e9c52 | ||
|
|
3d98558bf6 | ||
|
|
e7e7f518bf | ||
|
|
cb06c8778a | ||
|
|
ab5c205a7f | ||
|
|
2f85902d2c | ||
|
|
b75e78bdda | ||
|
|
182a0251a4 | ||
|
|
89881c64a6 | ||
|
|
7321e2a159 | ||
|
|
9b45aa3bc9 | ||
|
|
dfadac08d3 | ||
|
|
2e87f0bd9f | ||
|
|
7f8086545c | ||
|
|
de6e3c2010 | ||
|
|
eb6d0125c6 | ||
|
|
e8f754c10b | ||
|
|
a72bbf2f58 | ||
|
|
c465f51e66 | ||
|
|
599e658742 | ||
|
|
929a669cad | ||
|
|
503b8b5176 | ||
|
|
d2aa727c12 | ||
|
|
d66dcecd2f | ||
|
|
80d0c44b40 | ||
|
|
0e5cd30d01 | ||
|
|
56b9fe3998 | ||
|
|
a22f1298eb | ||
|
|
b7bd6a2846 | ||
|
|
a52ab171de | ||
|
|
13a7508f26 | ||
|
|
a499aa4cc5 | ||
|
|
e7207878d4 | ||
|
|
47d64c1cf5 | ||
|
|
c75dc86263 | ||
|
|
105a8d1a3c | ||
|
|
c74cf52e38 | ||
|
|
5c9b448b14 | ||
|
|
4e95495dcc | ||
|
|
82f86f5fbe | ||
|
|
f737d6e158 | ||
|
|
f9feb94668 | ||
|
|
112afa7e51 | ||
|
|
1694ea38ab | ||
|
|
0f3b7caa0f | ||
|
|
a85e1e1e15 | ||
|
|
55d00027a9 | ||
|
|
bf012619a6 | ||
|
|
f47795bf40 | ||
|
|
848e7a2f85 | ||
|
|
ecc6138833 | ||
|
|
d6600eb876 | ||
|
|
bc237de755 | ||
|
|
89abb51734 | ||
|
|
be8570edac | ||
|
|
09cce6802d | ||
|
|
c164dd7dcf | ||
|
|
00435fb27d | ||
|
|
0ebaa0b991 | ||
|
|
0d7b529233 | ||
|
|
2c2f7e8e98 | ||
|
|
768b8a2417 | ||
|
|
192714629c | ||
|
|
f2f761e8db | ||
|
|
410aa14b28 | ||
|
|
e857293414 | ||
|
|
492f911930 | ||
|
|
0002d84a6c | ||
|
|
9b49b38496 | ||
|
|
7cf9294e09 | ||
|
|
129f73aa4f | ||
|
|
7bdb93784b | ||
|
|
b32c001fec | ||
|
|
5f134d7b99 | ||
|
|
81b0823632 | ||
|
|
95e3af7487 | ||
|
|
77047bab2a | ||
|
|
c1cf6d65de | ||
|
|
f626fc2f89 | ||
|
|
f56b1b4530 | ||
|
|
1c423c4b6c | ||
|
|
7715b143d2 | ||
|
|
b200417903 | ||
|
|
138d80d57d | ||
|
|
07a33447ad | ||
|
|
4d11d28f96 | ||
|
|
e61dc12eab | ||
|
|
2c268d906c | ||
|
|
0acaebe067 | ||
|
|
9ea109f705 | ||
|
|
be2964562f | ||
|
|
6d7bf1b878 | ||
|
|
3a7c963f3c | ||
|
|
3728928a6d | ||
|
|
596eeee59d | ||
|
|
ae2f8a384f | ||
|
|
ffe0989969 | ||
|
|
53eeb26e28 | ||
|
|
0325d2a7f6 | ||
|
|
b83d568dfb | ||
|
|
2f839b3362 | ||
|
|
bfc655d6ab | ||
|
|
bd63ea9484 | ||
|
|
6c825625fb | ||
|
|
be59c2da90 | ||
|
|
8a4e6730a8 | ||
|
|
6053475c6f | ||
|
|
ed1cfc28aa | ||
|
|
1637aea0fa | ||
|
|
3c5aa05b48 | ||
|
|
5a1dc5a992 | ||
|
|
ccbf8201a0 | ||
|
|
34be9f681a | ||
|
|
cfca229500 | ||
|
|
d2396a8162 | ||
|
|
b630514906 | ||
|
|
f8dbbf7757 | ||
|
|
8eb26db72d | ||
|
|
499cbeaa62 | ||
|
|
4b103161b9 | ||
|
|
fb8e4829c3 | ||
|
|
4a81220aa3 | ||
|
|
85a0d751c0 | ||
|
|
859195be94 | ||
|
|
8499002072 | ||
|
|
6e85a2e1d9 | ||
|
|
e4b4197a9a | ||
|
|
60f34bf2ae | ||
|
|
63afc7d7be | ||
|
|
0aec952fd2 | ||
|
|
f8b8374058 | ||
|
|
d62bb11a9e | ||
|
|
af39ecbc5f | ||
|
|
6c2330fc6c | ||
|
|
109da5fa38 |
15
.env
15
.env
@@ -1,12 +1,10 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME=''
|
ACCESS_TOKEN_COOKIE_NAME=''
|
||||||
|
ACCOUNT_PROFILE_URL=''
|
||||||
BASE_URL=''
|
BASE_URL=''
|
||||||
COACHING_ENABLED=''
|
|
||||||
CREDENTIALS_BASE_URL=''
|
CREDENTIALS_BASE_URL=''
|
||||||
CSRF_TOKEN_API_PATH=''
|
CSRF_TOKEN_API_PATH=''
|
||||||
DEMOGRAPHICS_BASE_URL=''
|
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL=''
|
ECOMMERCE_BASE_URL=''
|
||||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
|
||||||
FAVICON_URL=''
|
FAVICON_URL=''
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME=''
|
LANGUAGE_PREFERENCE_COOKIE_NAME=''
|
||||||
LMS_BASE_URL=''
|
LMS_BASE_URL=''
|
||||||
@@ -14,9 +12,11 @@ LOGIN_URL=''
|
|||||||
LOGO_TRADEMARK_URL=''
|
LOGO_TRADEMARK_URL=''
|
||||||
LOGO_URL=''
|
LOGO_URL=''
|
||||||
LOGO_WHITE_URL=''
|
LOGO_WHITE_URL=''
|
||||||
|
SHOW_PUSH_CHANNEL=''
|
||||||
|
SHOW_EMAIL_CHANNEL=''
|
||||||
LOGOUT_URL=''
|
LOGOUT_URL=''
|
||||||
MARKETING_SITE_BASE_URL=''
|
MARKETING_SITE_BASE_URL=''
|
||||||
NODE_ENV=''
|
NODE_ENV='production'
|
||||||
ORDER_HISTORY_URL=''
|
ORDER_HISTORY_URL=''
|
||||||
PUBLISHER_BASE_URL=''
|
PUBLISHER_BASE_URL=''
|
||||||
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
REFRESH_ACCESS_TOKEN_ENDPOINT=''
|
||||||
@@ -26,7 +26,14 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL=''
|
SUPPORT_URL=''
|
||||||
USER_INFO_COOKIE_NAME=''
|
USER_INFO_COOKIE_NAME=''
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
|
ENABLE_ACCOUNT_DELETION=''
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
|
PASSWORD_RESET_SUPPORT_LINK=''
|
||||||
|
LEARNER_FEEDBACK_URL=''
|
||||||
|
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
|
ACCOUNT_PROFILE_URL='http://localhost:1995'
|
||||||
BASE_URL='localhost:1997'
|
BASE_URL='localhost:1997'
|
||||||
COACHING_ENABLED=''
|
|
||||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
@@ -27,8 +25,16 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL='http://localhost:18000/support'
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
|
ENABLE_ACCOUNT_DELETION=''
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
|
SHOW_PUSH_CHANNEL='true'
|
||||||
|
SHOW_EMAIL_CHANNEL='true'
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
|
PASSWORD_RESET_SUPPORT_LINK='mailto:support@example.com'
|
||||||
|
LEARNER_FEEDBACK_URL=''
|
||||||
|
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
||||||
|
# Fallback in local style files
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
|
|||||||
10
.env.test
10
.env.test
@@ -1,12 +1,9 @@
|
|||||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||||
BASE_URL='localhost:1997'
|
BASE_URL='localhost:1997'
|
||||||
COACHING_ENABLED=''
|
|
||||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||||
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
|
||||||
DEMOGRAPHICS_BASE_URL='http://localhost:18360'
|
|
||||||
DISCOVERY_API_BASE_URL=''
|
DISCOVERY_API_BASE_URL=''
|
||||||
ECOMMERCE_BASE_URL='http://localhost:18130'
|
ECOMMERCE_BASE_URL='http://localhost:18130'
|
||||||
ENABLE_DEMOGRAPHICS_COLLECTION=''
|
|
||||||
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
|
||||||
LMS_BASE_URL='http://localhost:18000'
|
LMS_BASE_URL='http://localhost:18000'
|
||||||
@@ -26,7 +23,14 @@ STUDIO_BASE_URL=''
|
|||||||
SUPPORT_URL='http://localhost:18000/support'
|
SUPPORT_URL='http://localhost:18000/support'
|
||||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||||
ENABLE_COPPA_COMPLIANCE=''
|
ENABLE_COPPA_COMPLIANCE=''
|
||||||
|
ENABLE_ACCOUNT_DELETION=''
|
||||||
|
SHOW_PUSH_CHANNEL=''
|
||||||
|
SHOW_EMAIL_CHANNEL=''
|
||||||
ENABLE_DOB_UPDATE=''
|
ENABLE_DOB_UPDATE=''
|
||||||
MARKETING_EMAILS_OPT_IN=''
|
MARKETING_EMAILS_OPT_IN=''
|
||||||
APP_ID=
|
APP_ID=
|
||||||
MFE_CONFIG_API_URL=
|
MFE_CONFIG_API_URL=
|
||||||
|
LEARNER_FEEDBACK_URL=''
|
||||||
|
SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT='https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account'
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED='[]'
|
||||||
|
PARAGON_THEME_URLS={}
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ dist/
|
|||||||
node_modules/
|
node_modules/
|
||||||
__mocks__/
|
__mocks__/
|
||||||
__snapshots__/
|
__snapshots__/
|
||||||
|
src/i18n/messages/
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
const { createConfig } = require('@edx/frontend-build');
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('eslint');
|
module.exports = createConfig('eslint');
|
||||||
|
|||||||
24
.github/pull_request_template.md
vendored
Normal file
24
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
### Description
|
||||||
|
|
||||||
|
Include a description of your changes here, along with a link to any relevant Jira tickets and/or GitHub issues.
|
||||||
|
|
||||||
|
#### How Has This Been Tested?
|
||||||
|
|
||||||
|
Please describe in detail how you tested your changes.
|
||||||
|
|
||||||
|
#### Screenshots/sandbox (optional):
|
||||||
|
Include a link to the sandbox for design changes or screenshot for before and after. **Remove this section if it's not applicable.**
|
||||||
|
|
||||||
|
|Before|After|
|
||||||
|
|-------|-----|
|
||||||
|
| | |
|
||||||
|
|
||||||
|
#### Merge Checklist
|
||||||
|
|
||||||
|
* [ ] If your update includes visual changes, have they been reviewed by a designer? Send them a link to the Sandbox, if applicable.
|
||||||
|
* [ ] Is there adequate test coverage for your changes?
|
||||||
|
|
||||||
|
#### Post-merge Checklist
|
||||||
|
|
||||||
|
* [ ] Deploy the changes to prod after verifying on stage or ask **@jacobo-dominguez-wgu** to do it.
|
||||||
|
* [ ] 🎉 🙌 Celebrate! Thanks for your contribution.
|
||||||
@@ -16,4 +16,4 @@ jobs:
|
|||||||
secrets:
|
secrets:
|
||||||
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||||
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
||||||
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
||||||
|
|||||||
20
.github/workflows/add-remove-label-on-comment.yml
vendored
Normal file
20
.github/workflows/add-remove-label-on-comment.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# This workflow runs when a comment is made on the ticket
|
||||||
|
# If the comment starts with "label: " it tries to apply
|
||||||
|
# the label indicated in rest of comment.
|
||||||
|
# If the comment starts with "remove label: ", it tries
|
||||||
|
# to remove the indicated label.
|
||||||
|
# Note: Labels are allowed to have spaces and this script does
|
||||||
|
# not parse spaces (as often a space is legitimate), so the command
|
||||||
|
# "label: really long lots of words label" will apply the
|
||||||
|
# label "really long lots of words label"
|
||||||
|
|
||||||
|
name: Allows for the adding and removing of labels via comment
|
||||||
|
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add_remove_labels:
|
||||||
|
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master
|
||||||
|
|
||||||
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -9,22 +9,21 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: [16]
|
|
||||||
npm: [8.5.0]
|
|
||||||
npm-test:
|
npm-test:
|
||||||
- i18n_extract
|
- i18n_extract
|
||||||
- lint
|
- lint
|
||||||
- test
|
- test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version-file: '.nvmrc'
|
||||||
- run: npm install -g npm@${{ matrix.npm }}
|
|
||||||
- run: make requirements
|
- run: make requirements
|
||||||
- run: make test NPM_TESTS=build
|
- run: make test NPM_TESTS=build
|
||||||
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
- run: make test NPM_TESTS=${{ matrix.npm-test }}
|
||||||
- name: upload coverage
|
- name: Coverage
|
||||||
uses: codecov/codecov-action@v3
|
if: matrix.npm-test == 'test'
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
with:
|
with:
|
||||||
fail_ci_if_error: false
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
fail_ci_if_error: true
|
||||||
|
|||||||
3
.github/workflows/lockfileversion-check.yml
vendored
3
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,5 +10,4 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
version-check:
|
version-check:
|
||||||
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
|
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||||
|
|
||||||
|
|||||||
12
.github/workflows/self-assign-issue.yml
vendored
Normal file
12
.github/workflows/self-assign-issue.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# This workflow runs when a comment is made on the ticket
|
||||||
|
# If the comment starts with "assign me" it assigns the author to the
|
||||||
|
# ticket (case insensitive)
|
||||||
|
|
||||||
|
name: Assign comment author to ticket if they say "assign me"
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
self_assign_by_comment:
|
||||||
|
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master
|
||||||
41
.github/workflows/update-browserlist.yml
vendored
41
.github/workflows/update-browserlist.yml
vendored
@@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
12
.github/workflows/update-browserslist-db.yml
vendored
Normal file
12
.github/workflows/update-browserslist-db.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: Update Browserslist DB
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-browserslist:
|
||||||
|
uses: openedx/.github/.github/workflows/update-browserslist-db.yml@master
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
requirements_bot_github_token: ${{ secrets.requirements_bot_github_token }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -17,3 +17,4 @@ temp/babel-plugin-react-intl
|
|||||||
/temp
|
/temp
|
||||||
/.vscode
|
/.vscode
|
||||||
/module.config.js
|
/module.config.js
|
||||||
|
src/i18n/messages/
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
|
|
||||||
[o:open-edx:p:edx-platform:r:frontend-app-account]
|
|
||||||
file_filter = src/i18n/messages/<lang>.json
|
|
||||||
source_file = src/i18n/transifex_input.json
|
|
||||||
source_lang = en
|
|
||||||
type = KEYVALUEJSON
|
|
||||||
|
|
||||||
38
Makefile
38
Makefile
@@ -1,15 +1,10 @@
|
|||||||
export TRANSIFEX_RESOURCE = frontend-app-account
|
intl_imports = ./node_modules/.bin/intl-imports.js
|
||||||
transifex_resource = frontend-app-account
|
|
||||||
transifex_langs = "ar,fr,es_419,zh_CN,pt,it,de,uk,ru,hi,fr_CA"
|
|
||||||
|
|
||||||
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
i18n = ./src/i18n
|
i18n = ./src/i18n
|
||||||
transifex_input = $(i18n)/transifex_input.json
|
transifex_input = $(i18n)/transifex_input.json
|
||||||
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
|
|
||||||
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
|
|
||||||
|
|
||||||
# This directory must match .babelrc .
|
# This directory must match .babelrc .
|
||||||
transifex_temp = ./temp/babel-plugin-react-intl
|
transifex_temp = ./temp/babel-plugin-formatjs
|
||||||
|
|
||||||
NPM_TESTS=build i18n_extract lint test
|
NPM_TESTS=build i18n_extract lint test
|
||||||
|
|
||||||
@@ -22,6 +17,11 @@ test.npm.%: validate-no-uncommitted-package-lock-changes
|
|||||||
npm run $(*)
|
npm run $(*)
|
||||||
|
|
||||||
.PHONY: requirements
|
.PHONY: requirements
|
||||||
|
|
||||||
|
precommit:
|
||||||
|
npm run lint
|
||||||
|
npm audit
|
||||||
|
|
||||||
requirements: ## install ci requirements
|
requirements: ## install ci requirements
|
||||||
npm ci
|
npm ci
|
||||||
|
|
||||||
@@ -41,20 +41,18 @@ detect_changed_source_translations:
|
|||||||
# Checking for changed translations...
|
# Checking for changed translations...
|
||||||
git diff --exit-code $(i18n)
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
# Pushes translations to Transifex. You must run make extract_translations first.
|
|
||||||
push_translations:
|
|
||||||
# Pushing strings to Transifex...
|
|
||||||
tx push -s
|
|
||||||
# Fetching hashes from Transifex...
|
|
||||||
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
|
|
||||||
# Writing out comments to file...
|
|
||||||
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
|
|
||||||
# Pushing comments to Transifex...
|
|
||||||
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
|
|
||||||
|
|
||||||
# Pulls translations from Transifex.
|
|
||||||
pull_translations:
|
pull_translations:
|
||||||
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
|
rm -rf src/i18n/messages
|
||||||
|
mkdir src/i18n/messages
|
||||||
|
cd src/i18n/messages \
|
||||||
|
&& atlas pull $(ATLAS_OPTIONS) \
|
||||||
|
translations/frontend-platform/src/i18n/messages:frontend-platform \
|
||||||
|
translations/paragon/src/i18n/messages:paragon \
|
||||||
|
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
|
||||||
|
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
|
||||||
|
translations/frontend-app-account/src/i18n/messages:frontend-app-account
|
||||||
|
|
||||||
|
$(intl_imports) frontend-platform paragon frontend-component-header frontend-component-footer frontend-app-account
|
||||||
|
|
||||||
# This target is used by Travis.
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
|||||||
206
README.rst
206
README.rst
@@ -1,12 +1,13 @@
|
|||||||
|
####################
|
||||||
|
frontend-app-account
|
||||||
|
####################
|
||||||
|
|
||||||
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
|ci-badge| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||||
|
|
||||||
frontend-app-account
|
|
||||||
====================
|
|
||||||
|
|
||||||
Please tag **@edx/community-engineering** on any PRs or issues. Thanks!
|
********
|
||||||
|
Purpose
|
||||||
Introduction
|
********
|
||||||
------------
|
|
||||||
|
|
||||||
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
This is a micro-frontend application responsible for the display and updating of a user's account information.
|
||||||
|
|
||||||
@@ -15,37 +16,33 @@ What is the domain of this MFE?
|
|||||||
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
|
In this MFE: Private user settings UIs. Public facing profile is in a `separate MFE (Profile) <https://github.com/openedx/frontend-app-profile>`_
|
||||||
|
|
||||||
- Account settings page
|
- Account settings page
|
||||||
|
|
||||||
- Demographics collection
|
|
||||||
|
|
||||||
- IDV (Identity Verification)
|
- IDV (Identity Verification)
|
||||||
|
|
||||||
Installation
|
***************
|
||||||
------------
|
Getting Started
|
||||||
|
***************
|
||||||
|
|
||||||
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.
|
Prerequisites
|
||||||
|
=============
|
||||||
|
|
||||||
1. Install Devstack using the `Getting Started <https://github.com/openedx/devstack#getting-started>`_ instructions.
|
`Tutor`_ is currently recommended as a development environment for your
|
||||||
|
new MFE. Please refer
|
||||||
|
to the `relevant tutor-mfe documentation`_ to get started using it.
|
||||||
|
|
||||||
2. Start up Devstack, if it's not already started.
|
.. _Tutor: https://github.com/overhangio/tutor
|
||||||
|
|
||||||
3. Log in to Devstack (http://localhost:18000/login )
|
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#mfe-development
|
||||||
|
|
||||||
4. Within this project, install requirements and start the development server:
|
Plugins
|
||||||
|
=======
|
||||||
|
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
||||||
|
|
||||||
.. code-block::
|
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
|
||||||
|
|
||||||
npm install
|
|
||||||
npm start # The server will run on port 1997
|
|
||||||
|
|
||||||
5. Once the dev server is up, visit http://localhost:1997 to access the MFE
|
|
||||||
|
|
||||||
.. image:: ./docs/images/localhost_preview.png
|
|
||||||
|
|
||||||
Environment Variables/Setup Notes
|
Environment Variables/Setup Notes
|
||||||
---------------------------------
|
=================================
|
||||||
|
|
||||||
This MFE is configured via 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>`__.
|
This MFE is configured via the ``frontend-platform`` configuration module. For more information on MFE configuration see the `Configuration documentation`_.
|
||||||
|
|
||||||
The account settings micro-frontend also supports the following additional variable:
|
The account settings micro-frontend also supports the following additional variable:
|
||||||
|
|
||||||
@@ -55,28 +52,22 @@ Example: ``https://support.example.com``
|
|||||||
|
|
||||||
The fully-qualified URL to the support page in the target environment.
|
The fully-qualified URL to the support page in the target environment.
|
||||||
|
|
||||||
edX-specific Environment Variables
|
``PASSWORD_RESET_SUPPORT_LINK``
|
||||||
**********************************
|
|
||||||
|
|
||||||
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:
|
Examples:
|
||||||
|
|
||||||
``COACHING_ENABLED``
|
- ``https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-``
|
||||||
|
|
||||||
Example: ``true`` | ``''`` (empty strings are falsy)
|
- ``mailto:support@example.com``
|
||||||
|
|
||||||
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.
|
The fully-qualified URL to the support page or email to request the support from in the target environment.
|
||||||
|
|
||||||
``ENABLE_DEMOGRAPHICS_COLLECTION``
|
``ENABLE_ACCOUNT_DELETION``
|
||||||
|
|
||||||
Example: ``true`` | ``''`` (empty strings are falsy)
|
Example: ``'false'`` | ``''`` (empty strings are true)
|
||||||
|
|
||||||
Enables support for a section of the account settings page where a user can enter demographics information. Integrates with a private demographics service and is only used by edx.org.
|
Enable the account deletion option, defaults to true.
|
||||||
|
To disable account deletion set ``ENABLE_ACCOUNT_DELETION`` to ``'false'`` (string), otherwise it will default to true.
|
||||||
``DEMOGRAPHICS_BASE_URL``
|
|
||||||
|
|
||||||
Example: ``https://demographics.example.com``
|
|
||||||
|
|
||||||
Required only if ``ENABLE_DEMOGRAPHICS_COLLECTION`` is true. The fully-qualified URL to the private demographics service in the target environment.
|
|
||||||
|
|
||||||
Example build syntax with a single environment variable:
|
Example build syntax with a single environment variable:
|
||||||
|
|
||||||
@@ -84,21 +75,142 @@ Example build syntax with a single environment variable:
|
|||||||
|
|
||||||
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
NODE_ENV=development ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' npm run build
|
||||||
|
|
||||||
For more information see the document: `Micro-frontend applications in Open
|
For more information see the document: `Configuration documentation`_
|
||||||
edX <https://edx.readthedocs.io/projects/edx-developer-docs/en/latest/developers_guide/micro_frontends_in_open_edx.html#required-environment-variables>`__.
|
|
||||||
|
.. _Configuration documentation: https://openedx.github.io/frontend-platform/module-Config.html
|
||||||
|
|
||||||
|
Cloning and Startup
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
|
||||||
|
1. Clone your new repo:
|
||||||
|
|
||||||
|
``git clone https://github.com/openedx/frontend-app-account.git``
|
||||||
|
|
||||||
|
2. Use the version of Node specified in the ``.nvmrc`` file.
|
||||||
|
|
||||||
|
The current version of the micro-frontend build scripts supports the version of Node found in ``.nvmrc``.
|
||||||
|
Using other major versions of node *may* work, but this is unsupported. For
|
||||||
|
convenience, this repository includes an .nvmrc file to help in setting the
|
||||||
|
correct node version via `nvm <https://github.com/nvm-sh/nvm>`_.
|
||||||
|
|
||||||
|
3. Install npm dependencies:
|
||||||
|
|
||||||
|
``cd frontend-app-account && npm ci``
|
||||||
|
|
||||||
|
4. Start the dev server:
|
||||||
|
|
||||||
|
``npm start``
|
||||||
|
|
||||||
|
Or for local development with custom configuration:
|
||||||
|
|
||||||
|
``npm run dev``
|
||||||
|
|
||||||
|
This runs the dev server with PUBLIC_PATH=/account/, MFE_CONFIG_API_URL pointing to localhost:8000, and hosts on apps.local.openedx.io.
|
||||||
|
|
||||||
|
Local module development
|
||||||
|
=========================
|
||||||
|
|
||||||
|
To develop locally on modules that are installed into this app, you'll need to create a ``module.config.js``
|
||||||
|
file (which is git-ignored) that defines where to find your local modules, for instance:
|
||||||
|
|
||||||
|
.. code-block:: js
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/*
|
||||||
|
Modules you want to use from local source code. Adding a module here means that when this app
|
||||||
|
runs its build, it'll resolve the source from peer directories of this app.
|
||||||
|
|
||||||
|
moduleName: the name you use to import code from the module.
|
||||||
|
dir: The relative path to the module's source code.
|
||||||
|
dist: The sub-directory of the source code where it puts its build artifact. Often "dist", though you
|
||||||
|
may want to use "src" if the module installs React as a peer/dev dependency.
|
||||||
|
*/
|
||||||
|
localModules: [
|
||||||
|
{ moduleName: '@openedx/paragon/scss', dir: '../paragon', dist: 'scss' },
|
||||||
|
{ moduleName: '@openedx/paragon', dir: '../paragon', dist: 'dist' },
|
||||||
|
{ moduleName: '@openedx/frontend-enterprise', dir: '../frontend-enterprise', dist: 'src' },
|
||||||
|
{ moduleName: '@openedx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
See https://github.com/openedx/frontend-build#local-module-configuration-for-webpack for more details.
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
------------
|
===========
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
||||||
Development Roadmap
|
Development Roadmap
|
||||||
-------------------
|
===================
|
||||||
|
|
||||||
We don't have anything planned for the core of the MFE (the account settings page) - this MFE is currently in maintenance mode.
|
We don't have anything planned for the core of the MFE (the account settings page) - this MFE is currently in maintenance mode.
|
||||||
There may be a replacement for IDV coming down the pipe, so that may be DEPRed.
|
There may be a replacement for IDV coming down the pipe, so that may be DEPRed.
|
||||||
In the future, it's possible that demographics could be modeled as a plugin rather than being hard-coded into this MFE.
|
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
The code in this repository is licensed under the AGPLv3 unless otherwise
|
||||||
|
noted.
|
||||||
|
|
||||||
|
Please see `LICENSE <LICENSE>`_ for details.
|
||||||
|
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
Contributions are very welcome. Please read `How To Contribute`_ for details.
|
||||||
|
|
||||||
|
.. _How To Contribute: https://openedx.org/r/how-to-contribute
|
||||||
|
|
||||||
|
This project is currently accepting all types of contributions, bug fixes,
|
||||||
|
security fixes, maintenance work, or new features. However, please make sure
|
||||||
|
to have a discussion about your new feature idea with the maintainers prior to
|
||||||
|
beginning development to maximize the chances of your change being accepted.
|
||||||
|
You can start a conversation by creating a new issue on this repo summarizing
|
||||||
|
your idea.
|
||||||
|
|
||||||
|
|
||||||
|
Getting Help
|
||||||
|
===========
|
||||||
|
|
||||||
|
If you're having trouble, we have discussion forums at
|
||||||
|
https://discuss.openedx.org where you can connect with others in the community.
|
||||||
|
|
||||||
|
Our real-time conversations are on Slack. You can request a `Slack
|
||||||
|
invitation`_, then join our `community Slack workspace`_. Because this is a
|
||||||
|
frontend repository, the best place to discuss it would be in the `#wg-frontend
|
||||||
|
channel`_.
|
||||||
|
|
||||||
|
For anything non-trivial, the best path is to open an issue in this repository
|
||||||
|
with as many details about the issue you are facing as you can provide.
|
||||||
|
|
||||||
|
https://github.com/openedx/frontend-app-account/issues
|
||||||
|
|
||||||
|
For more information about these options, see the `Getting Help`_ page.
|
||||||
|
|
||||||
|
.. _Slack invitation: https://openedx.org/slack
|
||||||
|
.. _community Slack workspace: https://openedx.slack.com/
|
||||||
|
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
|
||||||
|
.. _Getting Help: https://openedx.org/community/connect
|
||||||
|
|
||||||
|
|
||||||
|
The Open edX Code of Conduct
|
||||||
|
============================
|
||||||
|
|
||||||
|
All community members are expected to follow the `Open edX Code of Conduct`_.
|
||||||
|
|
||||||
|
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
|
||||||
|
|
||||||
|
People
|
||||||
|
======
|
||||||
|
The assigned maintainers for this component and other project details may be found in Backstage or from inspecting catalog-info.yaml.
|
||||||
|
|
||||||
|
Reporting Security Issues
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Please do not report security issues in public. Please email security@openedx.org.
|
||||||
|
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
@@ -106,7 +218,7 @@ In the future, it's possible that demographics could be modeled as a plugin rath
|
|||||||
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
:target: https://github.com/openedx/edx-developer-docs/actions/workflows/ci.yml
|
||||||
:alt: Continuous Integration
|
:alt: Continuous Integration
|
||||||
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
|
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-app-account
|
||||||
:target: https://codecov.io/gh/edx/frontend-app-account
|
:target: https://codecov.io/gh/openedx/frontend-app-account/
|
||||||
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-account.svg
|
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-app-account.svg
|
||||||
:target: @edx/frontend-app-account
|
:target: @edx/frontend-app-account
|
||||||
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-account.svg
|
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-app-account.svg
|
||||||
|
|||||||
19
catalog-info.yaml
Normal file
19
catalog-info.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# This file records information about this repo. Its use is described in OEP-55:
|
||||||
|
# https://open-edx-proposals.readthedocs.io/en/latest/processes/oep-0055-proc-project-maintainers.html
|
||||||
|
|
||||||
|
apiVersion: backstage.io/v1alpha1
|
||||||
|
kind: Component
|
||||||
|
metadata:
|
||||||
|
name: 'frontend-app-account'
|
||||||
|
description: "Open edX micro-frontend application for managing user account information."
|
||||||
|
links:
|
||||||
|
- url: "https://github.com/openedx/frontend-app-account"
|
||||||
|
title: "Frontend app account"
|
||||||
|
icon: "Web"
|
||||||
|
annotations:
|
||||||
|
openedx.org/arch-interest-groups: ""
|
||||||
|
openedx.org/release: "master"
|
||||||
|
spec:
|
||||||
|
owner: jacobo-dominguez-wgu
|
||||||
|
type: 'website'
|
||||||
|
lifecycle: 'production'
|
||||||
15
codecov.yml
Normal file
15
codecov.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
coverage:
|
||||||
|
status:
|
||||||
|
project:
|
||||||
|
default:
|
||||||
|
enabled: yes
|
||||||
|
target: auto
|
||||||
|
threshold: 0%
|
||||||
|
patch:
|
||||||
|
default:
|
||||||
|
enabled: yes
|
||||||
|
target: auto
|
||||||
|
threshold: 0%
|
||||||
|
ignore:
|
||||||
|
- "src/i18n"
|
||||||
|
- "src/index.jsx"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const { createConfig } = require('@edx/frontend-build');
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
module.exports = createConfig('jest', {
|
module.exports = createConfig('jest', {
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
# This file describes this Open edX repo, as described in OEP-2:
|
|
||||||
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0002-bp-repo-metadata.html#specification
|
|
||||||
|
|
||||||
nick: acct
|
|
||||||
oeps: {}
|
|
||||||
openedx-release: {ref: master}
|
|
||||||
43676
package-lock.json
generated
43676
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
88
package.json
88
package.json
@@ -10,8 +10,10 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "fedx-scripts webpack",
|
"build": "fedx-scripts webpack",
|
||||||
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
|
"dev": "PUBLIC_PATH=/account/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
|
||||||
|
"i18n_extract": "fedx-scripts formatjs extract",
|
||||||
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"snapshot": "fedx-scripts jest --updateSnapshot",
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
"start": "fedx-scripts webpack-dev-server --progress",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
|
"test": "TZ=UTC fedx-scripts jest --coverage --passWithNoTests"
|
||||||
@@ -27,26 +29,27 @@
|
|||||||
"extends @edx/browserslist-config"
|
"extends @edx/browserslist-config"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
"@edx/frontend-component-footer": "11.5.2",
|
"@edx/frontend-component-footer": "^14.6.0",
|
||||||
"@edx/frontend-component-header": "3.5.0",
|
"@edx/frontend-component-header": "^6.2.0",
|
||||||
"@edx/frontend-platform": "2.6.2",
|
"@edx/frontend-platform": "^8.4.0",
|
||||||
"@edx/paragon": "19.25.3",
|
"@edx/openedx-atlas": "^0.7.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "^6.6.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||||
"@fortawesome/react-fontawesome": "0.2.0",
|
"@fortawesome/react-fontawesome": "0.2.6",
|
||||||
"@tensorflow-models/blazeface": "0.0.7",
|
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||||
"@tensorflow/tfjs-converter": "3.21.0",
|
"@openedx/paragon": "^23.4.5",
|
||||||
"@tensorflow/tfjs-core": "3.21.0",
|
"@tensorflow-models/blazeface": "0.1.0",
|
||||||
"bowser": "2.11.0",
|
"@tensorflow/tfjs-converter": "4.22.0",
|
||||||
"classnames": "2.3.2",
|
"@tensorflow/tfjs-core": "4.22.0",
|
||||||
"core-js": "3.26.1",
|
"bowser": "2.14.1",
|
||||||
|
"classnames": "2.5.1",
|
||||||
|
"core-js": "3.48.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"form-urlencoded": "4.5.1",
|
"form-urlencoded": "6.1.6",
|
||||||
"formdata-polyfill": "4.0.10",
|
"formdata-polyfill": "4.0.10",
|
||||||
"history": "4.10.1",
|
|
||||||
"jslib-html5-camera-photo": "3.3.4",
|
"jslib-html5-camera-photo": "3.3.4",
|
||||||
"lodash.camelcase": "4.3.0",
|
"lodash.camelcase": "4.3.0",
|
||||||
"lodash.debounce": "4.0.8",
|
"lodash.debounce": "4.0.8",
|
||||||
@@ -57,38 +60,35 @@
|
|||||||
"lodash.omit": "4.5.0",
|
"lodash.omit": "4.5.0",
|
||||||
"lodash.pick": "4.4.0",
|
"lodash.pick": "4.4.0",
|
||||||
"lodash.pickby": "4.6.0",
|
"lodash.pickby": "4.6.0",
|
||||||
"long": "5.2.1",
|
"lodash.snakecase": "4.1.1",
|
||||||
"memoize-one": "5.2.1",
|
"long": "5.3.2",
|
||||||
"prop-types": "15.7.2",
|
"memoize-one": "^6.0.0",
|
||||||
"qs": "6.10.3",
|
"prop-types": "15.8.1",
|
||||||
"react": "16.14.0",
|
"qs": "6.15.0",
|
||||||
"react-dom": "16.14.0",
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
"react-helmet": "6.1.0",
|
"react-helmet": "6.1.0",
|
||||||
"react-redux": "7.2.9",
|
"react-redux": "7.2.9",
|
||||||
"react-router": "5.2.1",
|
"react-router": "^6.25.1",
|
||||||
"react-router-dom": "5.3.0",
|
"react-router-dom": "^6.25.1",
|
||||||
"react-router-hash-link": "2.4.3",
|
"react-router-hash-link": "2.4.3",
|
||||||
"react-scrollspy": "3.4.3",
|
"react-scrollspy": "3.4.3",
|
||||||
"react-transition-group": "4.4.5",
|
"react-transition-group": "4.4.5",
|
||||||
"redux": "4.1.2",
|
"redux": "4.2.1",
|
||||||
"redux-devtools-extension": "2.13.9",
|
"redux-devtools-extension": "2.13.9",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-saga": "1.1.3",
|
"redux-saga": "1.4.2",
|
||||||
"redux-thunk": "2.3.0",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "0.13.11",
|
"regenerator-runtime": "0.14.1",
|
||||||
"reselect": "4.0.0",
|
"reselect": "^5.1.1",
|
||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "7.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@edx/browserslist-config": "1.1.1",
|
"@edx/browserslist-config": "1.5.1",
|
||||||
"@edx/frontend-build": "12.4.0",
|
"@openedx/frontend-build": "^14.6.2",
|
||||||
"@edx/reactifex": "1.1.0",
|
"@testing-library/jest-dom": "6.9.1",
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
"@testing-library/react": "14.3.1",
|
||||||
"@testing-library/react": "12.1.5",
|
"react-test-renderer": "^18.3.1",
|
||||||
"enzyme": "3.11.0",
|
"redux-mock-store": "1.5.5"
|
||||||
"enzyme-adapter-react-16": "1.15.7",
|
|
||||||
"react-test-renderer": "16.14.0",
|
|
||||||
"reactifex": "1.1.1",
|
|
||||||
"redux-mock-store": "1.5.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,148 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en-us">
|
<html lang="en-us">
|
||||||
<head>
|
<head>
|
||||||
<title>Account | <%= process.env.SITE_NAME %></title>
|
<title>Account | <%= process.env.SITE_NAME %></title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
|
<link
|
||||||
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
rel="shortcut icon"
|
||||||
<script
|
href="<%=htmlWebpackPlugin.options.FAVICON_URL%>"
|
||||||
src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"
|
type="image/x-icon"
|
||||||
></script>
|
/>
|
||||||
<% } %>
|
<% if (process.env.OPTIMIZELY_PROJECT_ID) { %>
|
||||||
|
<script src="<%= process.env.MARKETING_SITE_BASE_URL %>/optimizelyjs/<%= process.env.OPTIMIZELY_PROJECT_ID %>.js"></script>
|
||||||
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- begin usabilla live embed code -->
|
||||||
|
<script defer type="text/javascript">
|
||||||
|
window.lightningjs ||
|
||||||
|
(function (n) {
|
||||||
|
var e = "lightningjs";
|
||||||
|
function t(e, t) {
|
||||||
|
var r, i, a, o, d, c;
|
||||||
|
return (
|
||||||
|
t && (t += (/\?/.test(t) ? "&" : "?") + "lv=1"),
|
||||||
|
n[e] ||
|
||||||
|
((r = window),
|
||||||
|
(i = document),
|
||||||
|
(a = e),
|
||||||
|
(o = i.location.protocol),
|
||||||
|
(d = "load"),
|
||||||
|
(c = 0),
|
||||||
|
(function () {
|
||||||
|
n[a] = function () {
|
||||||
|
var t = arguments,
|
||||||
|
i = this,
|
||||||
|
o = ++c,
|
||||||
|
d = (i && i != r && i.id) || 0;
|
||||||
|
function s() {
|
||||||
|
return (s.id = o), n[a].apply(s, arguments);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(e.s = e.s || []).push([o, d, t]),
|
||||||
|
(s.then = function (n, t, r) {
|
||||||
|
var i = (e.fh[o] = e.fh[o] || []),
|
||||||
|
a = (e.eh[o] = e.eh[o] || []),
|
||||||
|
d = (e.ph[o] = e.ph[o] || []);
|
||||||
|
return (
|
||||||
|
n && i.push(n), t && a.push(t), r && d.push(r), s
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
s
|
||||||
|
);
|
||||||
|
};
|
||||||
|
var e = (n[a]._ = {});
|
||||||
|
function s() {
|
||||||
|
e.P(d), (e.w = 1), n[a]("_load");
|
||||||
|
}
|
||||||
|
(e.fh = {}),
|
||||||
|
(e.eh = {}),
|
||||||
|
(e.ph = {}),
|
||||||
|
(e.l = t
|
||||||
|
? t.replace(/^\/\//, ("https:" == o ? o : "http:") + "//")
|
||||||
|
: t),
|
||||||
|
(e.p = { 0: +new Date() }),
|
||||||
|
(e.P = function (n) {
|
||||||
|
e.p[n] = new Date() - e.p[0];
|
||||||
|
}),
|
||||||
|
e.w && s(),
|
||||||
|
r.addEventListener
|
||||||
|
? r.addEventListener(d, s, !1)
|
||||||
|
: r.attachEvent("onload", s);
|
||||||
|
var l = function () {
|
||||||
|
function n() {
|
||||||
|
return [
|
||||||
|
"<!DOCTYPE ",
|
||||||
|
o,
|
||||||
|
"><",
|
||||||
|
o,
|
||||||
|
"><head></head><",
|
||||||
|
t,
|
||||||
|
"><",
|
||||||
|
r,
|
||||||
|
' src="',
|
||||||
|
e.l,
|
||||||
|
'"></',
|
||||||
|
r,
|
||||||
|
"></",
|
||||||
|
t,
|
||||||
|
"></",
|
||||||
|
o,
|
||||||
|
">",
|
||||||
|
].join("");
|
||||||
|
}
|
||||||
|
var t = "body",
|
||||||
|
r = "script",
|
||||||
|
o = "html",
|
||||||
|
d = i[t];
|
||||||
|
if (!d) return setTimeout(l, 100);
|
||||||
|
e.P(1);
|
||||||
|
var c,
|
||||||
|
s = i.createElement("div"),
|
||||||
|
h = s.appendChild(i.createElement("div")),
|
||||||
|
u = i.createElement("iframe");
|
||||||
|
(s.style.display = "none"),
|
||||||
|
(d.insertBefore(s, d.firstChild).id = "lightningjs-" + a),
|
||||||
|
(u.frameBorder = "0"),
|
||||||
|
(u.id = "lightningjs-frame-" + a),
|
||||||
|
/MSIE[ ]+6/.test(navigator.userAgent) &&
|
||||||
|
(u.src = "javascript:false"),
|
||||||
|
(u.allowTransparency = "true"),
|
||||||
|
h.appendChild(u);
|
||||||
|
try {
|
||||||
|
u.contentWindow.document.open();
|
||||||
|
} catch (n) {
|
||||||
|
(e.domain = i.domain),
|
||||||
|
(c =
|
||||||
|
"javascript:var d=document.open();d.domain='" +
|
||||||
|
i.domain +
|
||||||
|
"';"),
|
||||||
|
(u.src = c + "void(0);");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var p = u.contentWindow.document;
|
||||||
|
p.write(n()), p.close();
|
||||||
|
} catch (e) {
|
||||||
|
u.src =
|
||||||
|
c +
|
||||||
|
'd.write("' +
|
||||||
|
n().replace(/"/g, String.fromCharCode(92) + '"') +
|
||||||
|
'");d.close();';
|
||||||
|
}
|
||||||
|
e.P(2);
|
||||||
|
};
|
||||||
|
e.l && l();
|
||||||
|
})()),
|
||||||
|
(n[e].lv = "1"),
|
||||||
|
n[e]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var r = (window.lightningjs = t(e));
|
||||||
|
(r.require = t), (r.modules = n);
|
||||||
|
})({});
|
||||||
|
</script>
|
||||||
|
<!-- end usabilla live embed code -->
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"automerge": true
|
"automerge": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchPackagePatterns": ["@edx"],
|
"matchPackagePatterns": ["@edx", "@openedx"],
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"automerge": true
|
"automerge": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AppContext } from '@edx/frontend-platform/react';
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
import { getConfig, history, getQueryParameters } from '@edx/frontend-platform';
|
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@@ -14,9 +14,9 @@ import {
|
|||||||
getLanguageList,
|
getLanguageList,
|
||||||
} from '@edx/frontend-platform/i18n';
|
} from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, Hyperlink, Icon, Alert,
|
Container, Hyperlink, Icon, Alert,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { CheckCircle, Error, WarningFilled } from '@edx/paragon/icons';
|
import { CheckCircle, Error, WarningFilled } from '@openedx/paragon/icons';
|
||||||
|
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
import {
|
import {
|
||||||
@@ -45,24 +45,21 @@ import {
|
|||||||
GENDER_OPTIONS,
|
GENDER_OPTIONS,
|
||||||
COUNTRY_WITH_STATES,
|
COUNTRY_WITH_STATES,
|
||||||
COPPA_COMPLIANCE_YEAR,
|
COPPA_COMPLIANCE_YEAR,
|
||||||
|
WORK_EXPERIENCE_OPTIONS,
|
||||||
getStatesList,
|
getStatesList,
|
||||||
|
FIELD_LABELS,
|
||||||
} from './data/constants';
|
} from './data/constants';
|
||||||
import { fetchSiteLanguages } from './site-language';
|
import { fetchSiteLanguages } from './site-language';
|
||||||
import CoachingToggle from './coaching/CoachingToggle';
|
import { fetchNotificationPreferences } from '../notification-preferences/data/thunks';
|
||||||
import DemographicsSection from './demographics/DemographicsSection';
|
import NotificationSettings from '../notification-preferences/NotificationSettings';
|
||||||
|
import { withLocation, withNavigate } from './hoc';
|
||||||
|
import AdditionalProfileFieldsSlot from '../plugin-slots/AdditionalProfileFieldsSlot';
|
||||||
|
|
||||||
class AccountSettingsPage extends React.Component {
|
class AccountSettingsPage extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
// If there is a "duplicate_provider" query parameter, that's the backend's
|
|
||||||
// way of telling us that the provider account the user tried to link is already linked
|
|
||||||
// to another user account on the platform. We use this to display a message to that effect,
|
|
||||||
// and remove the parameter from the URL.
|
|
||||||
const duplicateTpaProvider = getQueryParameters().duplicate_provider;
|
const duplicateTpaProvider = getQueryParameters().duplicate_provider;
|
||||||
if (duplicateTpaProvider !== undefined) {
|
|
||||||
history.replace(history.location.pathname);
|
|
||||||
}
|
|
||||||
this.state = {
|
this.state = {
|
||||||
duplicateTpaProvider,
|
duplicateTpaProvider,
|
||||||
};
|
};
|
||||||
@@ -70,8 +67,8 @@ class AccountSettingsPage extends React.Component {
|
|||||||
this.navLinkRefs = {
|
this.navLinkRefs = {
|
||||||
'#basic-information': React.createRef(),
|
'#basic-information': React.createRef(),
|
||||||
'#profile-information': React.createRef(),
|
'#profile-information': React.createRef(),
|
||||||
'#demographics-information': React.createRef(),
|
|
||||||
'#social-media': React.createRef(),
|
'#social-media': React.createRef(),
|
||||||
|
'#notifications': React.createRef(),
|
||||||
'#site-preferences': React.createRef(),
|
'#site-preferences': React.createRef(),
|
||||||
'#linked-accounts': React.createRef(),
|
'#linked-accounts': React.createRef(),
|
||||||
'#delete-account': React.createRef(),
|
'#delete-account': React.createRef(),
|
||||||
@@ -79,8 +76,9 @@ class AccountSettingsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
this.props.fetchNotificationPreferences();
|
||||||
this.props.fetchSettings();
|
this.props.fetchSettings();
|
||||||
this.props.fetchSiteLanguages();
|
this.props.fetchSiteLanguages(this.props.navigate);
|
||||||
sendTrackingLogEvent('edx.user.settings.viewed', {
|
sendTrackingLogEvent('edx.user.settings.viewed', {
|
||||||
page: 'account',
|
page: 'account',
|
||||||
visibility: null,
|
visibility: null,
|
||||||
@@ -126,7 +124,15 @@ class AccountSettingsPage extends React.Component {
|
|||||||
countryOptions: [{
|
countryOptions: [{
|
||||||
value: '',
|
value: '',
|
||||||
label: this.props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
|
label: this.props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
|
||||||
}].concat(getCountryList(locale).map(({ code, name }) => ({ value: code, label: name }))),
|
}].concat(
|
||||||
|
this.removeDisabledCountries(
|
||||||
|
getCountryList(locale).map(({ code, name }) => ({
|
||||||
|
value: code,
|
||||||
|
label: name,
|
||||||
|
disabled: this.isDisabledCountry(code),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
),
|
||||||
stateOptions: [{
|
stateOptions: [{
|
||||||
value: '',
|
value: '',
|
||||||
label: this.props.intl.formatMessage(messages['account.settings.field.state.options.empty']),
|
label: this.props.intl.formatMessage(messages['account.settings.field.state.options.empty']),
|
||||||
@@ -147,14 +153,47 @@ class AccountSettingsPage extends React.Component {
|
|||||||
value: key,
|
value: key,
|
||||||
label: this.props.intl.formatMessage(messages[`account.settings.field.gender.options.${key || 'empty'}`]),
|
label: this.props.intl.formatMessage(messages[`account.settings.field.gender.options.${key || 'empty'}`]),
|
||||||
})),
|
})),
|
||||||
|
workExperienceOptions: WORK_EXPERIENCE_OPTIONS.map(key => ({
|
||||||
|
value: key,
|
||||||
|
label: key === '' ? this.props.intl.formatMessage(messages['account.settings.field.work.experience.options.empty']) : key,
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
canDeleteAccount = () => {
|
||||||
|
const { committedValues } = this.props;
|
||||||
|
return !getConfig().COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED.includes(committedValues.country);
|
||||||
|
};
|
||||||
|
|
||||||
|
removeDisabledCountries = (countryList) => {
|
||||||
|
const { countriesCodesList, committedValues } = this.props;
|
||||||
|
const committedCountry = committedValues?.country;
|
||||||
|
|
||||||
|
if (!countriesCodesList.length) {
|
||||||
|
return countryList;
|
||||||
|
}
|
||||||
|
return countryList.filter(({ value }) => value === committedCountry || countriesCodesList.find(x => x === value));
|
||||||
|
};
|
||||||
|
|
||||||
handleEditableFieldChange = (name, value) => {
|
handleEditableFieldChange = (name, value) => {
|
||||||
this.props.updateDraft(name, value);
|
this.props.updateDraft(name, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (formId, values) => {
|
handleSubmit = (formId, values) => {
|
||||||
this.props.saveSettings(formId, values);
|
if (formId === FIELD_LABELS.COUNTRY && this.isDisabledCountry(values)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { formValues } = this.props;
|
||||||
|
let extendedProfileObject = {};
|
||||||
|
|
||||||
|
if ('extended_profile' in formValues && formValues.extended_profile.some((field) => field.field_name === formId)) {
|
||||||
|
extendedProfileObject = {
|
||||||
|
extended_profile: formValues.extended_profile.map(field => (field.field_name === formId
|
||||||
|
? { ...field, field_value: values }
|
||||||
|
: field)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.props.saveSettings(formId, values, extendedProfileObject);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmitProfileName = (formId, values) => {
|
handleSubmitProfileName = (formId, values) => {
|
||||||
@@ -185,6 +224,12 @@ class AccountSettingsPage extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isDisabledCountry = (country) => {
|
||||||
|
const { countriesCodesList } = this.props;
|
||||||
|
|
||||||
|
return countriesCodesList.length > 0 && !countriesCodesList.find(x => x === country);
|
||||||
|
};
|
||||||
|
|
||||||
isEditable(fieldName) {
|
isEditable(fieldName) {
|
||||||
return !this.props.staticFields.includes(fieldName);
|
return !this.props.staticFields.includes(fieldName);
|
||||||
}
|
}
|
||||||
@@ -200,6 +245,12 @@ class AccountSettingsPage extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is a "duplicate_provider" query parameter, that's the backend's
|
||||||
|
// way of telling us that the provider account the user tried to link is already linked
|
||||||
|
// to another user account on the platform. We use this to display a message to that effect,
|
||||||
|
// and remove the parameter from the URL.
|
||||||
|
this.props.navigate(this.props.location, { replace: true });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Alert variant="danger">
|
<Alert variant="danger">
|
||||||
@@ -297,19 +348,9 @@ class AccountSettingsPage extends React.Component {
|
|||||||
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.header'])}
|
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message.header'])}
|
||||||
body={
|
body={
|
||||||
(
|
(
|
||||||
<>
|
<div className="d-flex flex-row">
|
||||||
<div className="d-flex flex-row">
|
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message'])}
|
||||||
{this.props.intl.formatMessage(messages['account.settings.field.name.verified.failure.message'])}
|
</div>
|
||||||
</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>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -444,16 +485,6 @@ class AccountSettingsPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDemographicsSection() {
|
|
||||||
// check the result of an LMS API call to determine if we should render the DemographicsSection component
|
|
||||||
if (this.props.formValues.shouldDisplayDemographicsSection) {
|
|
||||||
return (
|
|
||||||
<DemographicsSection forwardRef={this.navLinkRefs['#demographics-information']} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const editableFieldProps = {
|
const editableFieldProps = {
|
||||||
onChange: this.handleEditableFieldChange,
|
onChange: this.handleEditableFieldChange,
|
||||||
@@ -468,13 +499,16 @@ class AccountSettingsPage extends React.Component {
|
|||||||
yearOfBirthOptions,
|
yearOfBirthOptions,
|
||||||
educationLevelOptions,
|
educationLevelOptions,
|
||||||
genderOptions,
|
genderOptions,
|
||||||
|
workExperienceOptions,
|
||||||
} = this.getLocalizedOptions(this.context.locale, this.props.formValues.country);
|
} = this.getLocalizedOptions(this.context.locale, this.props.formValues.country);
|
||||||
|
|
||||||
// Show State field only if the country is US (could include Canada later)
|
// Show State field only if the country is US (could include Canada later)
|
||||||
const showState = this.props.formValues.country === COUNTRY_WITH_STATES;
|
const { country } = this.props.formValues;
|
||||||
|
const showState = country === COUNTRY_WITH_STATES && !this.isDisabledCountry(country);
|
||||||
const { verifiedName } = this.props;
|
const { verifiedName } = this.props;
|
||||||
|
|
||||||
|
const hasWorkExperience = !!this.props.formValues?.extended_profile?.find(field => field.field_name === 'work_experience');
|
||||||
|
|
||||||
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
|
||||||
this.props.timeZoneOptions,
|
this.props.timeZoneOptions,
|
||||||
this.props.countryTimeZoneOptions,
|
this.props.countryTimeZoneOptions,
|
||||||
@@ -678,6 +712,18 @@ class AccountSettingsPage extends React.Component {
|
|||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.gender.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.gender.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
|
{hasWorkExperience
|
||||||
|
&& (
|
||||||
|
<EditableSelectField
|
||||||
|
name="work_experience"
|
||||||
|
type="select"
|
||||||
|
value={this.props.formValues?.extended_profile?.find(field => field.field_name === 'work_experience')?.field_value}
|
||||||
|
options={workExperienceOptions}
|
||||||
|
label={this.props.intl.formatMessage(messages['account.settings.field.work.experience'])}
|
||||||
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.work.experience.empty'])}
|
||||||
|
{...editableFieldProps}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<EditableSelectField
|
<EditableSelectField
|
||||||
name="language_proficiencies"
|
name="language_proficiencies"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -687,18 +733,10 @@ class AccountSettingsPage extends React.Component {
|
|||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
{getConfig().COACHING_ENABLED
|
|
||||||
&& this.props.formValues.coaching.eligible_for_coaching
|
<AdditionalProfileFieldsSlot />
|
||||||
&& (
|
|
||||||
<CoachingToggle
|
|
||||||
name="coaching"
|
|
||||||
phone_number={this.props.formValues.phone_number}
|
|
||||||
coaching={this.props.formValues.coaching}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
|
<div className="account-section pt-3 mb-6" id="social-media">
|
||||||
<div className="account-section pt-3 mb-5" id="social-media">
|
|
||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
|
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -726,16 +764,19 @@ class AccountSettingsPage extends React.Component {
|
|||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
<EditableField
|
<EditableField
|
||||||
name="social_link_twitter"
|
name="social_link_x"
|
||||||
type="text"
|
type="text"
|
||||||
value={this.props.formValues.social_link_twitter}
|
value={this.props.formValues.social_link_x}
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter'])}
|
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter'])}
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter.empty'])}
|
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.xTwitter.empty'])}
|
||||||
{...editableFieldProps}
|
{...editableFieldProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border border-light-700" />
|
||||||
<div className="account-section pt-3 mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
<div className="mt-6" id="notifications" ref={this.navLinkRefs['#notifications']}>
|
||||||
|
<NotificationSettings />
|
||||||
|
</div>
|
||||||
|
<div className="account-section mb-5" id="site-preferences" ref={this.navLinkRefs['#site-preferences']}>
|
||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
{this.props.intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -777,13 +818,15 @@ class AccountSettingsPage extends React.Component {
|
|||||||
<ThirdPartyAuth />
|
<ThirdPartyAuth />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
{getConfig().ENABLE_ACCOUNT_DELETION && (
|
||||||
<DeleteAccount
|
<div className="account-section pt-3 mb-5" id="delete-account" ref={this.navLinkRefs['#delete-account']}>
|
||||||
isVerifiedAccount={this.props.isActive}
|
<DeleteAccount
|
||||||
hasLinkedTPA={hasLinkedTPA}
|
isVerifiedAccount={this.props.isActive}
|
||||||
/>
|
hasLinkedTPA={hasLinkedTPA}
|
||||||
</div>
|
canDeleteAccount={this.canDeleteAccount()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -812,7 +855,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page__account-settings container-fluid py-5">
|
<Container className="page__account-settings py-5" size="xl">
|
||||||
{this.renderDuplicateTpaProviderMessage()}
|
{this.renderDuplicateTpaProviderMessage()}
|
||||||
<h1 className="mb-4">
|
<h1 className="mb-4">
|
||||||
{this.props.intl.formatMessage(messages['account.settings.page.heading'])}
|
{this.props.intl.formatMessage(messages['account.settings.page.heading'])}
|
||||||
@@ -820,9 +863,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
<div>
|
<div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-md-3">
|
<div className="col-md-3">
|
||||||
<JumpNav
|
<JumpNav />
|
||||||
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="col-md-9">
|
<div className="col-md-9">
|
||||||
{loading ? this.renderLoading() : null}
|
{loading ? this.renderLoading() : null}
|
||||||
@@ -831,7 +872,7 @@ class AccountSettingsPage extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -850,25 +891,23 @@ AccountSettingsPage.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
secondary_email: PropTypes.string,
|
secondary_email: PropTypes.string,
|
||||||
secondary_email_enabled: PropTypes.bool,
|
secondary_email_enabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
||||||
year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
country: PropTypes.string,
|
country: PropTypes.string,
|
||||||
level_of_education: PropTypes.string,
|
level_of_education: PropTypes.string,
|
||||||
gender: PropTypes.string,
|
gender: PropTypes.string,
|
||||||
|
extended_profile: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
field_name: PropTypes.string,
|
||||||
|
field_value: PropTypes.string,
|
||||||
|
})),
|
||||||
language_proficiencies: PropTypes.string,
|
language_proficiencies: PropTypes.string,
|
||||||
pending_name_change: PropTypes.string,
|
pending_name_change: PropTypes.string,
|
||||||
phone_number: PropTypes.string,
|
phone_number: PropTypes.string,
|
||||||
social_link_linkedin: PropTypes.string,
|
social_link_linkedin: PropTypes.string,
|
||||||
social_link_facebook: PropTypes.string,
|
social_link_facebook: PropTypes.string,
|
||||||
social_link_twitter: PropTypes.string,
|
social_link_x: PropTypes.string,
|
||||||
time_zone: PropTypes.string,
|
time_zone: PropTypes.string,
|
||||||
coaching: PropTypes.shape({
|
|
||||||
coaching_consent: PropTypes.bool.isRequired,
|
|
||||||
user: PropTypes.number.isRequired,
|
|
||||||
eligible_for_coaching: PropTypes.bool.isRequired,
|
|
||||||
}),
|
|
||||||
state: PropTypes.string,
|
state: PropTypes.string,
|
||||||
shouldDisplayDemographicsSection: PropTypes.bool,
|
|
||||||
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
useVerifiedNameForCerts: PropTypes.bool.isRequired,
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
@@ -876,6 +915,7 @@ AccountSettingsPage.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
useVerifiedNameForCerts: PropTypes.bool,
|
useVerifiedNameForCerts: PropTypes.bool,
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
|
country: PropTypes.string,
|
||||||
}),
|
}),
|
||||||
drafts: PropTypes.shape({}),
|
drafts: PropTypes.shape({}),
|
||||||
formErrors: PropTypes.shape({
|
formErrors: PropTypes.shape({
|
||||||
@@ -908,12 +948,16 @@ AccountSettingsPage.propTypes = {
|
|||||||
saveSettings: PropTypes.func.isRequired,
|
saveSettings: PropTypes.func.isRequired,
|
||||||
fetchSettings: PropTypes.func.isRequired,
|
fetchSettings: PropTypes.func.isRequired,
|
||||||
beginNameChange: PropTypes.func.isRequired,
|
beginNameChange: PropTypes.func.isRequired,
|
||||||
|
fetchNotificationPreferences: PropTypes.func.isRequired,
|
||||||
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
tpaProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||||
connected: PropTypes.bool,
|
connected: PropTypes.bool,
|
||||||
})),
|
})),
|
||||||
nameChangeModal: PropTypes.shape({
|
nameChangeModal: PropTypes.oneOfType([
|
||||||
formId: PropTypes.string,
|
PropTypes.shape({
|
||||||
}),
|
formId: PropTypes.string,
|
||||||
|
}),
|
||||||
|
PropTypes.bool,
|
||||||
|
]),
|
||||||
verifiedName: PropTypes.shape({
|
verifiedName: PropTypes.shape({
|
||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
@@ -931,6 +975,14 @@ AccountSettingsPage.propTypes = {
|
|||||||
proctored_exam_attempt_id: PropTypes.number,
|
proctored_exam_attempt_id: PropTypes.number,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
navigate: PropTypes.func.isRequired,
|
||||||
|
location: PropTypes.string.isRequired,
|
||||||
|
countriesCodesList: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
}),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
AccountSettingsPage.defaultProps = {
|
AccountSettingsPage.defaultProps = {
|
||||||
@@ -940,6 +992,7 @@ AccountSettingsPage.defaultProps = {
|
|||||||
committedValues: {
|
committedValues: {
|
||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
verified_name: null,
|
verified_name: null,
|
||||||
|
country: '',
|
||||||
},
|
},
|
||||||
drafts: {},
|
drafts: {},
|
||||||
formErrors: {},
|
formErrors: {},
|
||||||
@@ -952,17 +1005,19 @@ AccountSettingsPage.defaultProps = {
|
|||||||
tpaProviders: [],
|
tpaProviders: [],
|
||||||
isActive: true,
|
isActive: true,
|
||||||
secondary_email_enabled: false,
|
secondary_email_enabled: false,
|
||||||
nameChangeModal: {},
|
nameChangeModal: {} || false,
|
||||||
verifiedName: null,
|
verifiedName: null,
|
||||||
mostRecentVerifiedName: {},
|
mostRecentVerifiedName: {},
|
||||||
verifiedNameHistory: [],
|
verifiedNameHistory: [],
|
||||||
|
countriesCodesList: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(accountSettingsPageSelector, {
|
export default withLocation(withNavigate(connect(accountSettingsPageSelector, {
|
||||||
|
fetchNotificationPreferences,
|
||||||
fetchSettings,
|
fetchSettings,
|
||||||
saveSettings,
|
saveSettings,
|
||||||
saveMultipleSettings,
|
saveMultipleSettings,
|
||||||
updateDraft,
|
updateDraft,
|
||||||
fetchSiteLanguages,
|
fetchSiteLanguages,
|
||||||
beginNameChange,
|
beginNameChange,
|
||||||
})(injectIntl(AccountSettingsPage));
|
})(injectIntl(AccountSettingsPage))));
|
||||||
|
|||||||
@@ -46,11 +46,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Profile Information',
|
defaultMessage: 'Profile Information',
|
||||||
description: 'The profile information section heading.',
|
description: 'The profile information section heading.',
|
||||||
},
|
},
|
||||||
'account.settings.section.demographics.information': {
|
|
||||||
id: 'account.settings.section.demographics.information',
|
|
||||||
defaultMessage: 'Optional Information',
|
|
||||||
description: 'The optional information section heading.',
|
|
||||||
},
|
|
||||||
'account.settings.section.site.preferences': {
|
'account.settings.section.site.preferences': {
|
||||||
id: 'account.settings.section.site.preferences',
|
id: 'account.settings.section.site.preferences',
|
||||||
defaultMessage: 'Site Preferences',
|
defaultMessage: 'Site Preferences',
|
||||||
@@ -146,11 +141,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.',
|
defaultMessage: 'Once your proctored exam passes review, this name will appear on your certificate and public-facing records. Verified Name cannot be changed at this time.',
|
||||||
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring and will appear on certificates.',
|
description: 'Help text for the account settings verified name field when a verified name has been submitted through proctoring and will appear on certificates.',
|
||||||
},
|
},
|
||||||
'account.settings.field.name.verified.verification.alert': {
|
|
||||||
id: 'account.settings.field.name.verified.verification.help',
|
|
||||||
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
|
|
||||||
description: 'Form label instructing the user to enter the name on their ID.',
|
|
||||||
},
|
|
||||||
'account.settings.field.full.name.help.text.submitted': {
|
'account.settings.field.full.name.help.text.submitted': {
|
||||||
id: 'account.settings.field.full.name.help.text.submitted',
|
id: 'account.settings.field.full.name.help.text.submitted',
|
||||||
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Full name cannot be changed at this time.',
|
||||||
@@ -411,8 +401,8 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'No formal education',
|
defaultMessage: 'No formal education',
|
||||||
description: 'Selected by the user to describe their education.',
|
description: 'Selected by the user to describe their education.',
|
||||||
},
|
},
|
||||||
'account.settings.field.education.levels.o': {
|
'account.settings.field.education.levels.other': {
|
||||||
id: 'account.settings.field.education.levels.o',
|
id: 'account.settings.field.education.levels.other',
|
||||||
defaultMessage: 'Other education',
|
defaultMessage: 'Other education',
|
||||||
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
description: 'Selected by the user if they have a type of education not described by the other choices.',
|
||||||
},
|
},
|
||||||
@@ -519,15 +509,15 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Delete My Account',
|
defaultMessage: 'Delete My Account',
|
||||||
description: 'Header for the user account deletion area',
|
description: 'Header for the user account deletion area',
|
||||||
},
|
},
|
||||||
'account.settings.field.social.platform.name.twitter': {
|
'account.settings.field.social.platform.name.xTwitter': {
|
||||||
id: 'account.settings.field.social.platform.name.twitter',
|
id: 'account.settings.field.social.platform.name.xTwitter',
|
||||||
defaultMessage: 'Twitter',
|
defaultMessage: 'X (Twitter)',
|
||||||
description: 'Label for Twitter',
|
description: 'Label for X (Twitter)',
|
||||||
},
|
},
|
||||||
'account.settings.field.social.platform.name.twitter.empty': {
|
'account.settings.field.social.platform.name.xTwitter.empty': {
|
||||||
id: 'account.settings.field.social.platform.name.twitter.empty',
|
id: 'account.settings.field.social.platform.name.xTwitter.empty',
|
||||||
defaultMessage: 'Add Twitter profile',
|
defaultMessage: 'Add X profile',
|
||||||
description: 'Placeholder for an empty Twitter field',
|
description: 'Placeholder for an empty X field',
|
||||||
},
|
},
|
||||||
|
|
||||||
'account.settings.field.social.platform.name.facebook': {
|
'account.settings.field.social.platform.name.facebook': {
|
||||||
@@ -565,6 +555,26 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'No value set.',
|
defaultMessage: 'No value set.',
|
||||||
description: 'The placeholder for an empty but uneditable field when there is no administrator',
|
description: 'The placeholder for an empty but uneditable field when there is no administrator',
|
||||||
},
|
},
|
||||||
|
'notification.preferences.notifications.label': {
|
||||||
|
id: 'notification.preferences.notifications.label',
|
||||||
|
defaultMessage: 'Notifications',
|
||||||
|
description: 'Label for Notifications',
|
||||||
|
},
|
||||||
|
'account.settings.field.work.experience': {
|
||||||
|
id: 'account.settings.work.experience',
|
||||||
|
defaultMessage: 'Work Experience',
|
||||||
|
description: 'Label for account settings Work experience field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.work.experience.empty': {
|
||||||
|
id: 'account.settings.field.work.experience.empty',
|
||||||
|
defaultMessage: 'Add work experience',
|
||||||
|
description: 'Placeholder for empty account settings work experience field.',
|
||||||
|
},
|
||||||
|
'account.settings.field.work.experience.options.empty': {
|
||||||
|
id: 'account.settings.field.work.experience.options.empty',
|
||||||
|
defaultMessage: 'Select work experience',
|
||||||
|
description: 'Placeholder for the work experience levels dropdown.',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default messages;
|
export default messages;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { AppContext } from '@edx/frontend-platform/react';
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Button, Hyperlink } from '@edx/paragon';
|
import { Button, Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
import { betaLanguageBannerSelector } from './data/selectors';
|
import { betaLanguageBannerSelector } from './data/selectors';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
@@ -49,6 +49,9 @@ class BetaLanguageBanner extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const savedLanguage = this.getSiteLanguageEntry(this.context.locale);
|
const savedLanguage = this.getSiteLanguageEntry(this.context.locale);
|
||||||
|
if (!savedLanguage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const isSavedLanguageReleased = savedLanguage.released === true;
|
const isSavedLanguageReleased = savedLanguage.released === true;
|
||||||
const noPreviousLanguageSet = this.props.siteLanguage.previousValue === null;
|
const noPreviousLanguageSet = this.props.siteLanguage.previousValue === null;
|
||||||
if (isSavedLanguageReleased || noPreviousLanguageSet) {
|
if (isSavedLanguageReleased || noPreviousLanguageSet) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
Form, StatefulButton, ModalDialog, ActionRow, useToggle, Button,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
import { YEAR_OF_BIRTH_OPTIONS } from './data/constants';
|
||||||
@@ -11,11 +11,11 @@ import { editableFieldSelector } from './data/selectors';
|
|||||||
import { saveSettingsReset } from './data/actions';
|
import { saveSettingsReset } from './data/actions';
|
||||||
|
|
||||||
const DOBModal = (props) => {
|
const DOBModal = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
const {
|
const {
|
||||||
saveState,
|
saveState,
|
||||||
error,
|
error,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
intl,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -56,7 +56,7 @@ const DOBModal = (props) => {
|
|||||||
function renderErrors() {
|
function renderErrors() {
|
||||||
if (saveState === 'error' || error) {
|
if (saveState === 'error' || error) {
|
||||||
return (
|
return (
|
||||||
<Form.Control.Feedback type="invalid" key="general-error">
|
<Form.Control.Feedback type="invalid" key="general-error" data-testid="error-message">
|
||||||
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
|
{intl.formatMessage(messages['account.settingsfield.dob.error.general'])}
|
||||||
</Form.Control.Feedback>
|
</Form.Control.Feedback>
|
||||||
);
|
);
|
||||||
@@ -72,7 +72,7 @@ const DOBModal = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button variant="primary" onClick={open}>
|
<Button variant="primary" onClick={open} data-testid="open-modal-button">
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
|
{intl.formatMessage(messages['account.settings.field.dob.form.button'])}
|
||||||
</Button>
|
</Button>
|
||||||
<ModalDialog
|
<ModalDialog
|
||||||
@@ -81,25 +81,27 @@ const DOBModal = (props) => {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
hasCloseButton={false}
|
hasCloseButton={false}
|
||||||
variant="default"
|
variant="default"
|
||||||
|
data-testid="dob-modal"
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} data-testid="dob-form">
|
||||||
|
|
||||||
<ModalDialog.Header>
|
<ModalDialog.Header>
|
||||||
<ModalDialog.Title>
|
<ModalDialog.Title data-testid="modal-title">
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
{intl.formatMessage(messages['account.settings.field.dob.form.title'])}
|
||||||
</ModalDialog.Title>
|
</ModalDialog.Title>
|
||||||
</ModalDialog.Header>
|
</ModalDialog.Header>
|
||||||
|
|
||||||
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
|
<ModalDialog.Body className="overflow-hidden" style={{ padding: '1.5rem' }}>
|
||||||
<p>{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
|
<p data-testid="help-text">{intl.formatMessage(messages['account.settings.field.dob.form.help.text'])}</p>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label>
|
<Form.Label data-testid="month-label">
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.month'])}
|
{intl.formatMessage(messages['account.settings.field.dob.month'])}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
name="month"
|
name="month"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
data-testid="month-select"
|
||||||
>
|
>
|
||||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.month.default'])}</option>
|
<option value="">{intl.formatMessage(messages['account.settings.field.dob.month.default'])}</option>
|
||||||
{[...Array(12).keys()].map(month => (
|
{[...Array(12).keys()].map(month => (
|
||||||
@@ -108,13 +110,14 @@ const DOBModal = (props) => {
|
|||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label>
|
<Form.Label data-testid="year-label">
|
||||||
{intl.formatMessage(messages['account.settings.field.dob.year'])}
|
{intl.formatMessage(messages['account.settings.field.dob.year'])}
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
as="select"
|
as="select"
|
||||||
name="year"
|
name="year"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
data-testid="year-select"
|
||||||
>
|
>
|
||||||
<option value="">{intl.formatMessage(messages['account.settings.field.dob.year.default'])}</option>
|
<option value="">{intl.formatMessage(messages['account.settings.field.dob.year.default'])}</option>
|
||||||
{YEAR_OF_BIRTH_OPTIONS.map(year => (
|
{YEAR_OF_BIRTH_OPTIONS.map(year => (
|
||||||
@@ -127,7 +130,7 @@ const DOBModal = (props) => {
|
|||||||
|
|
||||||
<ModalDialog.Footer>
|
<ModalDialog.Footer>
|
||||||
<ActionRow>
|
<ActionRow>
|
||||||
<ModalDialog.CloseButton variant="tertiary">
|
<ModalDialog.CloseButton variant="tertiary" data-testid="cancel-button">
|
||||||
Cancel
|
Cancel
|
||||||
</ModalDialog.CloseButton>
|
</ModalDialog.CloseButton>
|
||||||
<StatefulButton
|
<StatefulButton
|
||||||
@@ -137,6 +140,7 @@ const DOBModal = (props) => {
|
|||||||
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
default: intl.formatMessage(messages['account.settings.editable.field.action.save']),
|
||||||
}}
|
}}
|
||||||
disabledStates={['unedited']}
|
disabledStates={['unedited']}
|
||||||
|
data-testid="submit-button"
|
||||||
/>
|
/>
|
||||||
</ActionRow>
|
</ActionRow>
|
||||||
</ModalDialog.Footer>
|
</ModalDialog.Footer>
|
||||||
@@ -151,7 +155,6 @@ DOBModal.propTypes = {
|
|||||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
||||||
error: PropTypes.string,
|
error: PropTypes.string,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DOBModal.defaultProps = {
|
DOBModal.defaultProps = {
|
||||||
@@ -159,4 +162,4 @@ DOBModal.defaultProps = {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(editableFieldSelector)(injectIntl(DOBModal));
|
export default connect(editableFieldSelector)(DOBModal);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import classNames from 'classnames';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, Form, StatefulButton,
|
Button, Form, StatefulButton,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@ const EditableField = (props) => {
|
|||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
isGrayedOut,
|
isGrayedOut,
|
||||||
intl,
|
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -84,9 +84,13 @@ const EditableField = (props) => {
|
|||||||
if (!confirmationMessageDefinition || !confirmationValue) {
|
if (!confirmationMessageDefinition || !confirmationValue) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return intl.formatMessage(confirmationMessageDefinition, {
|
return (
|
||||||
value: confirmationValue,
|
<span data-testid="editable-field-confirmation">
|
||||||
});
|
{intl.formatMessage(confirmationMessageDefinition, {
|
||||||
|
value: confirmationValue,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -95,7 +99,7 @@ const EditableField = (props) => {
|
|||||||
cases={{
|
cases={{
|
||||||
editing: (
|
editing: (
|
||||||
<>
|
<>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit} data-testid="editable-field-form">
|
||||||
<Form.Group
|
<Form.Group
|
||||||
controlId={id}
|
controlId={id}
|
||||||
isInvalid={error != null}
|
isInvalid={error != null}
|
||||||
@@ -108,10 +112,11 @@ const EditableField = (props) => {
|
|||||||
type={type}
|
type={type}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
data-testid="editable-field-textbox"
|
||||||
{...others}
|
{...others}
|
||||||
/>
|
/>
|
||||||
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
{!!helpText && <Form.Text>{helpText}</Form.Text>}
|
||||||
{error != null && <Form.Control.Feedback hasIcon={false}>{error}</Form.Control.Feedback>}
|
{error != null && <Form.Control.Feedback hasIcon={false} data-testid="editable-field-error">{error}</Form.Control.Feedback>}
|
||||||
{others.children}
|
{others.children}
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<p>
|
<p>
|
||||||
@@ -133,16 +138,21 @@ const EditableField = (props) => {
|
|||||||
if (saveState === 'pending') { e.preventDefault(); }
|
if (saveState === 'pending') { e.preventDefault(); }
|
||||||
}}
|
}}
|
||||||
disabledStates={[]}
|
disabledStates={[]}
|
||||||
|
data-testid="editable-field-save"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline-primary"
|
variant="outline-primary"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
|
data-testid="editable-field-cancel"
|
||||||
|
data-clicked="cancel"
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
{intl.formatMessage(messages['account.settings.editable.field.action.cancel'])}
|
||||||
</Button>
|
</Button>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{['name', 'verified_name'].includes(name) && <CertificatePreference fieldName={name} />}
|
{['name', 'verified_name'].includes(name) && (
|
||||||
|
<CertificatePreference fieldName={name} data-testid="editable-field-certificate-preference" />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
default: (
|
default: (
|
||||||
@@ -150,12 +160,12 @@ const EditableField = (props) => {
|
|||||||
<div className="d-flex align-items-start">
|
<div className="d-flex align-items-start">
|
||||||
<h6 aria-level="3">{label}</h6>
|
<h6 aria-level="3">{label}</h6>
|
||||||
{isEditable ? (
|
{isEditable ? (
|
||||||
<Button variant="link" onClick={handleEdit} className="ml-3">
|
<Button variant="link" onClick={handleEdit} className="ml-3" data-testid="editable-field-edit" data-clicked="edit">
|
||||||
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />{intl.formatMessage(messages['account.settings.editable.field.action.edit'])}
|
<FontAwesomeIcon className="mr-1" icon={faPencilAlt} />{intl.formatMessage(messages['account.settings.editable.field.action.edit'])}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
|
<p data-hj-suppress className={classNames('text-truncate', { 'grayed-out': isGrayedOut })}>{renderValue(value)}</p>
|
||||||
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -187,7 +197,6 @@ EditableField.propTypes = {
|
|||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
isGrayedOut: PropTypes.bool,
|
isGrayedOut: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EditableField.defaultProps = {
|
EditableField.defaultProps = {
|
||||||
@@ -208,4 +217,4 @@ EditableField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(injectIntl(EditableField));
|
})(EditableField);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, Form, StatefulButton,
|
Button, Form, StatefulButton,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
@@ -19,6 +18,7 @@ import { editableFieldSelector } from './data/selectors';
|
|||||||
import CertificatePreference from './certificate-preference/CertificatePreference';
|
import CertificatePreference from './certificate-preference/CertificatePreference';
|
||||||
|
|
||||||
const EditableSelectField = (props) => {
|
const EditableSelectField = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
@@ -39,7 +39,6 @@ const EditableSelectField = (props) => {
|
|||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
isGrayedOut,
|
isGrayedOut,
|
||||||
intl,
|
|
||||||
...others
|
...others
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
@@ -98,9 +97,29 @@ const EditableSelectField = (props) => {
|
|||||||
value: confirmationValue,
|
value: confirmationValue,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const selectOptions = options.map(option => (
|
const selectOptions = options.map((option) => {
|
||||||
<option value={option.value} key={`${option.value}-${option.label}`}>{option.label}</option>
|
if (option.group) {
|
||||||
));
|
// If the option has a 'group' property, it represents an element with sub-options.
|
||||||
|
return (
|
||||||
|
<optgroup label={option.label} key={option.label}>
|
||||||
|
{option.group.map((subOption) => (
|
||||||
|
<option
|
||||||
|
value={subOption.value}
|
||||||
|
key={`${subOption.value}-${subOption.label}`}
|
||||||
|
disabled={subOption?.disabled}
|
||||||
|
>
|
||||||
|
{subOption.label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</optgroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<option value={option.value} key={`${option.value}-${option.label}`} disabled={option?.disabled}>
|
||||||
|
{option.label}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SwitchContent
|
<SwitchContent
|
||||||
@@ -207,7 +226,6 @@ EditableSelectField.propTypes = {
|
|||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
isGrayedOut: PropTypes.bool,
|
isGrayedOut: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EditableSelectField.defaultProps = {
|
EditableSelectField.defaultProps = {
|
||||||
@@ -229,4 +247,4 @@ EditableSelectField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(injectIntl(EditableSelectField));
|
})(EditableSelectField);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
Button, StatefulButton, Form,
|
Button, StatefulButton, Form, Tooltip, OverlayTrigger,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@@ -35,9 +34,9 @@ const EmailField = (props) => {
|
|||||||
onChange,
|
onChange,
|
||||||
isEditing,
|
isEditing,
|
||||||
isEditable,
|
isEditable,
|
||||||
intl,
|
|
||||||
} = props;
|
} = props;
|
||||||
const id = `field-${name}`;
|
const id = `field-${name}`;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -162,7 +161,16 @@ const EmailField = (props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<p data-hj-suppress>{renderValue()}</p>
|
<OverlayTrigger
|
||||||
|
placement="top"
|
||||||
|
overlay={(
|
||||||
|
<Tooltip id={`tooltip-${name}`} variant="light" className="d-sm-none">
|
||||||
|
{renderValue()}
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p data-hj-suppress className="text-truncate">{renderValue()}</p>
|
||||||
|
</OverlayTrigger>
|
||||||
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
|
{renderConfirmationMessage() || <p className="small text-muted mt-n2">{helpText}</p>}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -191,7 +199,6 @@ EmailField.propTypes = {
|
|||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
isEditable: PropTypes.bool,
|
isEditable: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
EmailField.defaultProps = {
|
EmailField.defaultProps = {
|
||||||
@@ -210,4 +217,4 @@ EmailField.defaultProps = {
|
|||||||
export default connect(editableFieldSelector, {
|
export default connect(editableFieldSelector, {
|
||||||
onEdit: openForm,
|
onEdit: openForm,
|
||||||
onCancel: closeForm,
|
onCancel: closeForm,
|
||||||
})(injectIntl(EmailField));
|
})(EmailField);
|
||||||
|
|||||||
@@ -1,32 +1,30 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { breakpoints, useWindowSize } from '@edx/paragon';
|
import { breakpoints, useWindowSize } from '@openedx/paragon';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { NavHashLink } from 'react-router-hash-link';
|
import { NavHashLink } from 'react-router-hash-link';
|
||||||
import Scrollspy from 'react-scrollspy';
|
import Scrollspy from 'react-scrollspy';
|
||||||
import messages from './AccountSettingsPage.messages';
|
import messages from './AccountSettingsPage.messages';
|
||||||
|
|
||||||
const JumpNav = ({
|
const JumpNav = () => {
|
||||||
intl,
|
const intl = useIntl();
|
||||||
displayDemographicsLink,
|
|
||||||
}) => {
|
|
||||||
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
const stickToTop = useWindowSize().width > breakpoints.small.minWidth;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
|
||||||
<Scrollspy
|
<Scrollspy
|
||||||
items={[
|
items={[
|
||||||
'basic-information',
|
'basic-information',
|
||||||
'profile-information',
|
'profile-information',
|
||||||
'demographics-information',
|
|
||||||
'social-media',
|
'social-media',
|
||||||
|
'notifications',
|
||||||
'site-preferences',
|
'site-preferences',
|
||||||
'linked-accounts',
|
'linked-accounts',
|
||||||
'delete-account',
|
'delete-account',
|
||||||
]}
|
]}
|
||||||
className="list-unstyled"
|
className="list-unstyled"
|
||||||
currentClassName="font-weight-bold"
|
currentClassName="font-weight-bold"
|
||||||
|
offset={-64}
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#basic-information">
|
<NavHashLink to="#basic-information">
|
||||||
@@ -38,19 +36,16 @@ const JumpNav = ({
|
|||||||
{intl.formatMessage(messages['account.settings.section.profile.information'])}
|
{intl.formatMessage(messages['account.settings.section.profile.information'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && displayDemographicsLink
|
|
||||||
&& (
|
|
||||||
<li>
|
|
||||||
<NavHashLink to="#demographics-information">
|
|
||||||
{intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
|
||||||
</NavHashLink>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#social-media">
|
<NavHashLink to="#social-media">
|
||||||
{intl.formatMessage(messages['account.settings.section.social.media'])}
|
{intl.formatMessage(messages['account.settings.section.social.media'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<NavHashLink to="#notifications">
|
||||||
|
{intl.formatMessage(messages['notification.preferences.notifications.label'])}
|
||||||
|
</NavHashLink>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<NavHashLink to="#site-preferences">
|
<NavHashLink to="#site-preferences">
|
||||||
{intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
{intl.formatMessage(messages['account.settings.section.site.preferences'])}
|
||||||
@@ -61,19 +56,17 @@ const JumpNav = ({
|
|||||||
{intl.formatMessage(messages['account.settings.section.linked.accounts'])}
|
{intl.formatMessage(messages['account.settings.section.linked.accounts'])}
|
||||||
</NavHashLink>
|
</NavHashLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
{getConfig().ENABLE_ACCOUNT_DELETION
|
||||||
<NavHashLink to="#delete-account">
|
&& (
|
||||||
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
<li>
|
||||||
</NavHashLink>
|
<NavHashLink to="#delete-account">
|
||||||
</li>
|
{intl.formatMessage(messages['account.settings.jump.nav.delete.account'])}
|
||||||
|
</NavHashLink>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</Scrollspy>
|
</Scrollspy>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
JumpNav.propTypes = {
|
export default JumpNav;
|
||||||
intl: intlShape.isRequired,
|
|
||||||
displayDemographicsLink: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(JumpNav);
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import React from 'react';
|
|||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const NotFoundPage = () => (
|
const NotFoundPage = () => (
|
||||||
<div className="container-fluid d-flex py-5 justify-content-center align-items-start text-center">
|
<div
|
||||||
|
className="container-fluid d-flex py-5 justify-content-center align-items-start text-center"
|
||||||
|
data-testid="not-found-page"
|
||||||
|
>
|
||||||
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
<p className="my-0 py-5 text-muted" style={{ maxWidth: '32em' }}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="error.notfound.message"
|
id="error.notfound.message"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Alert } from '@edx/paragon';
|
import { Alert } from '@openedx/paragon';
|
||||||
|
|
||||||
const OneTimeDismissibleAlert = (props) => {
|
const OneTimeDismissibleAlert = (props) => {
|
||||||
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { TransitionReplace } from '@edx/paragon';
|
import { TransitionReplace } from '@openedx/paragon';
|
||||||
|
|
||||||
const onChildExit = (htmlNode) => {
|
const onChildExit = (htmlNode) => {
|
||||||
// If the leaving child has focus, take control and redirect it
|
// If the leaving child has focus, take control and redirect it
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.custom-switch {
|
.custom-switch {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
@@ -44,3 +43,8 @@
|
|||||||
filter: alpha(opacity = 60); /* MSIE */
|
filter: alpha(opacity = 60); /* MSIE */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tooltip-email .small {
|
||||||
|
display: block;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
StatefulButton,
|
StatefulButton,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
closeForm,
|
closeForm,
|
||||||
@@ -22,7 +22,6 @@ import commonMessages from '../AccountSettingsPage.messages';
|
|||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const CertificatePreference = ({
|
const CertificatePreference = ({
|
||||||
intl,
|
|
||||||
fieldName,
|
fieldName,
|
||||||
originalFullName,
|
originalFullName,
|
||||||
originalVerifiedName,
|
originalVerifiedName,
|
||||||
@@ -33,6 +32,7 @@ const CertificatePreference = ({
|
|||||||
const [checked, setChecked] = useState(false);
|
const [checked, setChecked] = useState(false);
|
||||||
const [modalIsOpen, setModalIsOpen] = useState(false);
|
const [modalIsOpen, setModalIsOpen] = useState(false);
|
||||||
const formId = 'useVerifiedNameForCerts';
|
const formId = 'useVerifiedNameForCerts';
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleCheckboxChange = () => {
|
const handleCheckboxChange = () => {
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
@@ -155,7 +155,6 @@ const CertificatePreference = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
CertificatePreference.propTypes = {
|
CertificatePreference.propTypes = {
|
||||||
intl: intlShape.isRequired,
|
|
||||||
fieldName: PropTypes.string.isRequired,
|
fieldName: PropTypes.string.isRequired,
|
||||||
originalFullName: PropTypes.string,
|
originalFullName: PropTypes.string,
|
||||||
originalVerifiedName: PropTypes.string,
|
originalVerifiedName: PropTypes.string,
|
||||||
@@ -170,4 +169,4 @@ CertificatePreference.defaultProps = {
|
|||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(certPreferenceSelector)(injectIntl(CertificatePreference));
|
export default connect(certPreferenceSelector)(CertificatePreference);
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
|
||||||
|
import { postVerifiedNameConfig } from './service';
|
||||||
|
import { handleRequestError } from '../../data/utils';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('../../data/utils');
|
||||||
|
|
||||||
|
describe('postVerifiedNameConfig', () => {
|
||||||
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({
|
||||||
|
LMS_BASE_URL: 'http://testserver',
|
||||||
|
});
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({
|
||||||
|
post: mockPost,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts verified name config with useVerifiedNameForCerts = true', async () => {
|
||||||
|
const mockResponse = { data: { success: true } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await postVerifiedNameConfig('testuser', { useVerifiedNameForCerts: true });
|
||||||
|
|
||||||
|
expect(getConfig).toHaveBeenCalled();
|
||||||
|
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/api/edx_name_affirmation/v1/verified_name/config',
|
||||||
|
{
|
||||||
|
username: 'testuser',
|
||||||
|
use_verified_name_for_certs: true,
|
||||||
|
},
|
||||||
|
{ headers: { Accept: 'application/json' } },
|
||||||
|
);
|
||||||
|
expect(result).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts verified name config with useVerifiedNameForCerts = false', async () => {
|
||||||
|
const mockResponse = { data: { success: false } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await postVerifiedNameConfig('anotheruser', { useVerifiedNameForCerts: false });
|
||||||
|
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/api/edx_name_affirmation/v1/verified_name/config',
|
||||||
|
{
|
||||||
|
username: 'anotheruser',
|
||||||
|
use_verified_name_for_certs: false,
|
||||||
|
},
|
||||||
|
{ headers: { Accept: 'application/json' } },
|
||||||
|
);
|
||||||
|
expect(result).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleRequestError and throws when request fails', async () => {
|
||||||
|
const mockError = new Error('Request failed');
|
||||||
|
mockPost.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
handleRequestError.mockImplementation(() => {
|
||||||
|
throw mockError;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
postVerifiedNameConfig('erroruser', { useVerifiedNameForCerts: true }),
|
||||||
|
).rejects.toThrow('Request failed');
|
||||||
|
|
||||||
|
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {
|
import {
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
import { createMemoryHistory } from 'history';
|
|
||||||
|
|
||||||
import * as auth from '@edx/frontend-platform/auth';
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import messages from '../messages';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
||||||
ReactDOM.createPortal = node => node;
|
jest.mock('react-dom', () => ({
|
||||||
|
...jest.requireActual('react-dom'),
|
||||||
|
createPortal: jest.fn(node => node), // Mock portal behavior
|
||||||
|
}));
|
||||||
|
|
||||||
import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first
|
import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first
|
||||||
|
|
||||||
@@ -28,10 +30,6 @@ jest.mock('react-redux', () => ({
|
|||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
||||||
|
|
||||||
const history = createMemoryHistory();
|
|
||||||
|
|
||||||
const IntlCertificatePreference = injectIntl(CertificatePreference);
|
|
||||||
|
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
|
|
||||||
describe('NameChange', () => {
|
describe('NameChange', () => {
|
||||||
@@ -39,10 +37,10 @@ describe('NameChange', () => {
|
|||||||
let store = {};
|
let store = {};
|
||||||
const formId = 'useVerifiedNameForCerts';
|
const formId = 'useVerifiedNameForCerts';
|
||||||
const updateDraft = 'UPDATE_DRAFT';
|
const updateDraft = 'UPDATE_DRAFT';
|
||||||
const labelText = 'If checked, this name will appear on your certificates and public-facing records.';
|
const labelText = messages['account.settings.field.name.checkbox.certificate.select'].defaultMessage;
|
||||||
|
|
||||||
const reduxWrapper = children => (
|
const reduxWrapper = children => (
|
||||||
<Router history={history}>
|
<Router>
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<Provider store={store}>{children}</Provider>
|
<Provider store={store}>{children}</Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
@@ -57,7 +55,6 @@ describe('NameChange', () => {
|
|||||||
originalVerifiedName: 'edX Verified',
|
originalVerifiedName: 'edX Verified',
|
||||||
saveState: null,
|
saveState: null,
|
||||||
useVerifiedNameForCerts: false,
|
useVerifiedNameForCerts: false,
|
||||||
intl: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
@@ -77,7 +74,7 @@ describe('NameChange', () => {
|
|||||||
originalVerifiedName: '',
|
originalVerifiedName: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
const wrapper = render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -88,7 +85,7 @@ describe('NameChange', () => {
|
|||||||
useVerifiedNameForCerts: true,
|
useVerifiedNameForCerts: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(false);
|
expect(checkbox.checked).toEqual(false);
|
||||||
@@ -103,7 +100,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('triggers modal when attempting to uncheck checkbox', () => {
|
it('triggers modal when attempting to uncheck checkbox', () => {
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(true);
|
expect(checkbox.checked).toEqual(true);
|
||||||
@@ -115,7 +112,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates draft when changing radio value', () => {
|
it('updates draft when changing radio value', () => {
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -133,7 +130,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('clears draft on cancel', () => {
|
it('clears draft on cancel', () => {
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -146,7 +143,7 @@ describe('NameChange', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('submits', () => {
|
it('submits', () => {
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
fireEvent.click(checkbox);
|
fireEvent.click(checkbox);
|
||||||
@@ -154,7 +151,7 @@ describe('NameChange', () => {
|
|||||||
const submitButton = screen.getByText('Choose name');
|
const submitButton = screen.getByText('Choose name');
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
expect(mockDispatch).toHaveBeenCalledWith({
|
expect(mockDispatch).toHaveBeenCalledWith({
|
||||||
payload: { formId, commitValues: false },
|
payload: { formId, commitValues: false, extendedProfile: {} },
|
||||||
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -166,7 +163,7 @@ describe('NameChange', () => {
|
|||||||
useVerifiedNameForCerts: true,
|
useVerifiedNameForCerts: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<IntlCertificatePreference {...props} />));
|
render(reduxWrapper(<CertificatePreference {...props} />));
|
||||||
|
|
||||||
const checkbox = screen.getByLabelText(labelText);
|
const checkbox = screen.getByLabelText(labelText);
|
||||||
expect(checkbox.checked).toEqual(true);
|
expect(checkbox.checked).toEqual(true);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`NameChange does not render if there is no verified name 1`] = `
|
exports[`NameChange does not render if there is no verified name 1`] = `
|
||||||
Object {
|
{
|
||||||
"asFragment": [Function],
|
"asFragment": [Function],
|
||||||
"baseElement": <body>
|
"baseElement": <body>
|
||||||
<div />
|
<div />
|
||||||
|
|||||||
@@ -1,267 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Hyperlink } from '@edx/paragon';
|
|
||||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import get from 'lodash.get';
|
|
||||||
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
|
||||||
|
|
||||||
import PageLoading from '../PageLoading';
|
|
||||||
import CoachingConsentForm from './CoachingConsentForm';
|
|
||||||
import messages from './CoachingConsent.messages';
|
|
||||||
import LogoSVG from '../../logo.svg';
|
|
||||||
import { fetchSettings } from '../data/actions';
|
|
||||||
import { coachingConsentPageSelector } from '../data/selectors';
|
|
||||||
|
|
||||||
const Logo = ({ src, alt, ...attributes }) => <img src={src} alt={alt} {...attributes} />;
|
|
||||||
|
|
||||||
const SuccessMessage = (props) => (
|
|
||||||
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
|
|
||||||
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
|
|
||||||
<div className="h3">{props.header}</div>
|
|
||||||
<div>{props.message}</div>
|
|
||||||
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
|
|
||||||
{props.continue}
|
|
||||||
</Hyperlink>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const AutoRedirect = (props) => {
|
|
||||||
window.location.href = props.redirectUrl;
|
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
||||||
return <></>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const VIEWS = {
|
|
||||||
NOT_LOADED: 'NOT_LOADED',
|
|
||||||
LOADED: 'LOADED',
|
|
||||||
SUCCESS: 'SUCCESS',
|
|
||||||
SUCCESS_PENDING: 'SUCCESS_PENDING',
|
|
||||||
DECLINED: 'DECLINED',
|
|
||||||
DECLINE_PENDING: 'DECLINE_PENDING',
|
|
||||||
};
|
|
||||||
|
|
||||||
class CoachingConsent extends React.Component {
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
// Used to redirect back to the courseware.
|
|
||||||
const nextUrl = this.sanitizeForwardingUrl(getQueryParameters().next);
|
|
||||||
this.state = {
|
|
||||||
redirectUrl: nextUrl || `${getConfig().LMS_BASE_URL}/dashboard/`,
|
|
||||||
formErrors: {},
|
|
||||||
formSubmitted: false,
|
|
||||||
declineSubmitted: false,
|
|
||||||
submissionSuccess: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
this.declineCoaching = this.declineCoaching.bind(this);
|
|
||||||
this.patchUsingCoachingConsentForm = this.patchUsingCoachingConsentForm.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.fetchSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const fullName = e.target.fullName.value;
|
|
||||||
const phoneNumber = e.target.phoneNumber.value;
|
|
||||||
const body = {
|
|
||||||
coaching_consent: true,
|
|
||||||
consent_form_seen: true,
|
|
||||||
phone_number: phoneNumber,
|
|
||||||
full_name: fullName,
|
|
||||||
};
|
|
||||||
this.setState({
|
|
||||||
formErrors: {},
|
|
||||||
formSubmitted: true,
|
|
||||||
declineSubmitted: false,
|
|
||||||
}, () => this.patchUsingCoachingConsentForm(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
sanitizeForwardingUrl(url) {
|
|
||||||
// Redirect to root of MFE if invalid next param is sent
|
|
||||||
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async patchUsingCoachingConsentForm(body) {
|
|
||||||
const { userId } = getAuthenticatedUser();
|
|
||||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/coaching_consent/${userId}/`;
|
|
||||||
let formErrors = {};
|
|
||||||
const data = await getAuthenticatedHttpClient()
|
|
||||||
.patch(requestUrl, body)
|
|
||||||
.catch((error) => {
|
|
||||||
if (get(error, 'customAttributes.httpErrorResponseData')) {
|
|
||||||
formErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
|
||||||
} else {
|
|
||||||
formErrors = { full_name: 'Something went wrong. Please try again.' };
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
submissionSuccess: false,
|
|
||||||
formErrors,
|
|
||||||
formSubmitted: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (get(data, 'status') === 200) {
|
|
||||||
this.setState({ submissionSuccess: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declineCoaching(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const body = {
|
|
||||||
coaching_consent: false,
|
|
||||||
consent_form_seen: true,
|
|
||||||
};
|
|
||||||
this.setState({
|
|
||||||
formErrors: {},
|
|
||||||
formSubmitted: false,
|
|
||||||
declineSubmitted: true,
|
|
||||||
}, () => this.patchUsingCoachingConsentForm(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
renderView(currentView) {
|
|
||||||
switch (currentView) {
|
|
||||||
case VIEWS.NOT_LOADED:
|
|
||||||
return <PageLoading srMessage="" />;
|
|
||||||
case VIEWS.LOADED:
|
|
||||||
return (
|
|
||||||
<CoachingConsentForm
|
|
||||||
onSubmit={this.handleSubmit}
|
|
||||||
declineCoaching={this.declineCoaching}
|
|
||||||
formErrors={this.state.formErrors}
|
|
||||||
formValues={this.props.formValues}
|
|
||||||
redirectUrl={this.state.redirectUrl}
|
|
||||||
profileDataManager={this.props.profileDataManager}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case VIEWS.SUCCESS_PENDING:
|
|
||||||
return <PageLoading srMessage="Submitting..." />;
|
|
||||||
case VIEWS.SUCCESS:
|
|
||||||
return (
|
|
||||||
<SuccessMessage
|
|
||||||
continueUrl={this.state.redirectUrl}
|
|
||||||
header={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.header'])}
|
|
||||||
message={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.message'])}
|
|
||||||
continue={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.continue'])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case VIEWS.DECLINE_PENDING:
|
|
||||||
return <PageLoading srMessage="Redirecting..." />;
|
|
||||||
case VIEWS.DECLINED:
|
|
||||||
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
|
|
||||||
default:
|
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { loaded } = this.props;
|
|
||||||
const formHasErrors = Object.keys(this.state.formErrors).length > 0;
|
|
||||||
let currentView = null;
|
|
||||||
// This amount of logic was making the template very hard to read, so I broke it out into views.
|
|
||||||
if (!loaded) {
|
|
||||||
currentView = VIEWS.NOT_LOADED;
|
|
||||||
} else if (this.state.formSubmitted && !formHasErrors) {
|
|
||||||
if (this.state.submissionSuccess) {
|
|
||||||
currentView = VIEWS.SUCCESS;
|
|
||||||
} else {
|
|
||||||
currentView = VIEWS.SUCCESS_PENDING;
|
|
||||||
}
|
|
||||||
} else if (this.state.declineSubmitted && !formHasErrors) {
|
|
||||||
if (this.state.submissionSuccess) {
|
|
||||||
currentView = VIEWS.DECLINED;
|
|
||||||
} else {
|
|
||||||
currentView = VIEWS.DECLINE_PENDING;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentView = VIEWS.LOADED;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<div className="w-100 d-flex justify-content-center align-items-center shadow coaching-header">
|
|
||||||
<Logo
|
|
||||||
className="logo"
|
|
||||||
src={LogoSVG}
|
|
||||||
alt="Logo"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{this.renderView(currentView)}
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Logo.defaultProps = {
|
|
||||||
src: '',
|
|
||||||
alt: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
Logo.propTypes = {
|
|
||||||
src: PropTypes.string,
|
|
||||||
alt: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
SuccessMessage.defaultProps = {
|
|
||||||
header: '',
|
|
||||||
message: '',
|
|
||||||
continueUrl: '',
|
|
||||||
continue: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
SuccessMessage.propTypes = {
|
|
||||||
header: PropTypes.string,
|
|
||||||
message: PropTypes.string,
|
|
||||||
continueUrl: PropTypes.string,
|
|
||||||
continue: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
AutoRedirect.defaultProps = {
|
|
||||||
redirectUrl: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
AutoRedirect.propTypes = {
|
|
||||||
redirectUrl: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
CoachingConsent.defaultProps = {
|
|
||||||
loaded: false,
|
|
||||||
profileDataManager: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
CoachingConsent.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
loaded: PropTypes.bool,
|
|
||||||
formValues: PropTypes.shape({
|
|
||||||
name: PropTypes.string,
|
|
||||||
phone_number: PropTypes.string,
|
|
||||||
coaching: PropTypes.shape({
|
|
||||||
coaching_consent: PropTypes.bool.isRequired,
|
|
||||||
user: PropTypes.number.isRequired,
|
|
||||||
eligible_for_coaching: PropTypes.bool.isRequired,
|
|
||||||
consent_form_seen: PropTypes.bool.isRequired,
|
|
||||||
}),
|
|
||||||
}).isRequired,
|
|
||||||
formErrors: PropTypes.shape({
|
|
||||||
coaching: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
confirmationValues: PropTypes.shape({
|
|
||||||
coaching: PropTypes.shape({}),
|
|
||||||
name: PropTypes.shape({}),
|
|
||||||
phone_number: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
fetchSettings: PropTypes.func.isRequired,
|
|
||||||
profileDataManager: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(coachingConsentPageSelector, {
|
|
||||||
fetchSettings,
|
|
||||||
})(injectIntl(CoachingConsent));
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
'account.settings.coaching.consent.welcome.header': {
|
|
||||||
id: 'account.settings.coaching.consent.welcome.header',
|
|
||||||
defaultMessage: 'Let’s get started.',
|
|
||||||
description: 'The welcome header for consent form.',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.welcome.subheader': {
|
|
||||||
id: 'account.settings.coaching.consent.welcome.subheader',
|
|
||||||
defaultMessage: "We're here for you from start to finish",
|
|
||||||
description: 'The welcome subheader for consent form.',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.description': {
|
|
||||||
id: 'account.settings.coaching.consent.description',
|
|
||||||
defaultMessage: "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
|
|
||||||
description: 'Text describing what Coaching is.',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.text-messaging.disclaimer': {
|
|
||||||
id: 'account.settings.coaching.consent.text-messaging.disclaimer',
|
|
||||||
defaultMessage: '* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.',
|
|
||||||
description: 'Text describing what Coaching is.',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.accept-coaching': {
|
|
||||||
id: 'account.settings.coaching.consent.accept-coaching',
|
|
||||||
defaultMessage: 'Sign up for coaching',
|
|
||||||
description: 'Text to confirm coaching enablement',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.decline-coaching': {
|
|
||||||
id: 'account.settings.coaching.consent.decline-coaching',
|
|
||||||
defaultMessage: 'I prefer not to be contacted with free coaching services',
|
|
||||||
description: 'Text to decline coaching enablement',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.label.name': {
|
|
||||||
id: 'account.settings.coaching.consent.label.name',
|
|
||||||
defaultMessage: 'Please confirm your name',
|
|
||||||
description: 'Label for name input',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.label.phone-number': {
|
|
||||||
id: 'account.settings.coaching.consent.label.phone-number',
|
|
||||||
defaultMessage: 'Enter your mobile number',
|
|
||||||
description: 'Label for mobile phone number input',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.success.header': {
|
|
||||||
id: 'account.settings.coaching.consent.success.header',
|
|
||||||
defaultMessage: 'Success!',
|
|
||||||
description: 'Heading announcing that submission succeeded',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.success.message': {
|
|
||||||
id: 'account.settings.coaching.consent.success.message',
|
|
||||||
defaultMessage: "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
|
|
||||||
description: 'Text announcing that you have signed up and will receive texts',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.consent.success.continue': {
|
|
||||||
id: 'account.settings.coaching.consent.success.continue',
|
|
||||||
defaultMessage: 'Start my course',
|
|
||||||
description: 'Text that the user will be sent back to the courseware',
|
|
||||||
},
|
|
||||||
'account.settings.coaching.managed.support': {
|
|
||||||
id: 'account.settings.coaching.managed.support',
|
|
||||||
defaultMessage: 'support',
|
|
||||||
description: 'website support',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default messages;
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Form, Button, Hyperlink } from '@edx/paragon';
|
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import Alert from '../Alert';
|
|
||||||
import messages from './CoachingConsent.messages';
|
|
||||||
|
|
||||||
const ErrorMessage = (props) => <div className="alert-warning mb-2">{props.message}</div>;
|
|
||||||
|
|
||||||
const ManagedProfileAlert = ({ profileDataManager }) => (
|
|
||||||
<Alert className="alert alert-primary" role="alert">
|
|
||||||
<FormattedMessage
|
|
||||||
id="account.settings.coaching.managed.alert"
|
|
||||||
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
|
|
||||||
description="Alert message informing the user their account data is managed by a third party"
|
|
||||||
values={{
|
|
||||||
managerTitle: <b>{profileDataManager}</b>,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
);
|
|
||||||
const CoachingForm = (props) => (
|
|
||||||
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
|
|
||||||
<h2 className="h2">
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
|
|
||||||
</h2>
|
|
||||||
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
|
|
||||||
<div>
|
|
||||||
<form onSubmit={props.onSubmit}>
|
|
||||||
<div className="py-3">
|
|
||||||
{!!props.profileDataManager && (
|
|
||||||
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
|
|
||||||
)}
|
|
||||||
<ErrorMessage message={props.formErrors.full_name} />
|
|
||||||
<label className="h6" htmlFor="fullName">
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
|
|
||||||
</label>
|
|
||||||
<Form.Control
|
|
||||||
type="text"
|
|
||||||
name="full-name"
|
|
||||||
id="fullName"
|
|
||||||
disabled={!!props.profileDataManager}
|
|
||||||
defaultValue={props.formValues.name}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="py-3">
|
|
||||||
<ErrorMessage message={props.formErrors.phone_number} />
|
|
||||||
<label className="h6" htmlFor="phoneNumber">
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
|
|
||||||
</label>
|
|
||||||
<Form.Control
|
|
||||||
type="text"
|
|
||||||
name="phone_number"
|
|
||||||
id="phoneNumber"
|
|
||||||
defaultValue={props.formValues.phone_number}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className=" py-3">
|
|
||||||
<p className="small font-italic">
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<ErrorMessage message={props.formErrors.coaching} />
|
|
||||||
<div className="d-flex flex-column align-items-center">
|
|
||||||
<Button variant="outline-primary" className="w-100" type="submit">
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3">
|
|
||||||
<Hyperlink
|
|
||||||
className="mt-3 text-dark btn-link small"
|
|
||||||
destination={props.redirectUrl}
|
|
||||||
onClick={props.declineCoaching}
|
|
||||||
>
|
|
||||||
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
|
|
||||||
</Hyperlink>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
CoachingForm.defaultProps = {
|
|
||||||
formErrors: {
|
|
||||||
coaching: '',
|
|
||||||
name: '',
|
|
||||||
phone_number: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
CoachingForm.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
onSubmit: PropTypes.func.isRequired,
|
|
||||||
declineCoaching: PropTypes.func.isRequired,
|
|
||||||
formValues: PropTypes.shape({
|
|
||||||
name: PropTypes.string,
|
|
||||||
phone_number: PropTypes.string,
|
|
||||||
coaching: PropTypes.shape({
|
|
||||||
coaching_consent: PropTypes.bool.isRequired,
|
|
||||||
user: PropTypes.number.isRequired,
|
|
||||||
eligible_for_coaching: PropTypes.bool.isRequired,
|
|
||||||
consent_form_seen: PropTypes.bool.isRequired,
|
|
||||||
}),
|
|
||||||
}).isRequired,
|
|
||||||
formErrors: PropTypes.shape({
|
|
||||||
coaching: PropTypes.string,
|
|
||||||
full_name: PropTypes.string,
|
|
||||||
phone_number: PropTypes.string,
|
|
||||||
}),
|
|
||||||
redirectUrl: PropTypes.string.isRequired,
|
|
||||||
profileDataManager: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorMessage.defaultProps = {
|
|
||||||
message: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
ErrorMessage.propTypes = {
|
|
||||||
message: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
ManagedProfileAlert.propTypes = {
|
|
||||||
profileDataManager: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(CoachingForm);
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
|
||||||
import { Form } from '@edx/paragon';
|
|
||||||
import messages from './CoachingToggle.messages';
|
|
||||||
import { editableFieldSelector } from '../data/selectors';
|
|
||||||
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
|
|
||||||
import EditableField from '../EditableField';
|
|
||||||
|
|
||||||
const CoachingToggle = (props) => (
|
|
||||||
<>
|
|
||||||
<EditableField
|
|
||||||
name="phone_number"
|
|
||||||
type="text"
|
|
||||||
value={props.phone_number}
|
|
||||||
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
|
|
||||||
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
|
|
||||||
onChange={props.updateDraft}
|
|
||||||
onSubmit={() => {
|
|
||||||
const { coaching } = props;
|
|
||||||
if (coaching.coaching_consent === true) {
|
|
||||||
return props.saveMultipleSettings([
|
|
||||||
{
|
|
||||||
formId: 'coaching',
|
|
||||||
commitValues: {
|
|
||||||
...coaching,
|
|
||||||
phone_number: props.phone_number,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
formId: 'phone_number',
|
|
||||||
commitValues: props.phone_number,
|
|
||||||
},
|
|
||||||
], 'phone_number');
|
|
||||||
}
|
|
||||||
return props.saveSettings('phone_number', props.phone_number);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Form.Group
|
|
||||||
isInvalid={!!props.error}
|
|
||||||
className="custom-control custom-switch"
|
|
||||||
>
|
|
||||||
<Form.Switch
|
|
||||||
name={props.name}
|
|
||||||
className="custom-control-input"
|
|
||||||
disabled={props.saveState === 'pending'}
|
|
||||||
type="checkbox"
|
|
||||||
id="coachingConsent"
|
|
||||||
checked={props.coaching.coaching_consent}
|
|
||||||
helperText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
|
|
||||||
value={props.coaching.coaching_consent}
|
|
||||||
onChange={async (e) => {
|
|
||||||
const { name } = e.target;
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
const { user, eligible_for_coaching } = props.coaching;
|
|
||||||
const value = {
|
|
||||||
user,
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
eligible_for_coaching,
|
|
||||||
coaching_consent: e.target.checked,
|
|
||||||
};
|
|
||||||
props.saveSettings(name, value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}
|
|
||||||
</Form.Switch>
|
|
||||||
{!!props.error && (
|
|
||||||
<Form.Control.Feedback>
|
|
||||||
{props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
|
|
||||||
</Form.Control.Feedback>
|
|
||||||
)}
|
|
||||||
</Form.Group>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
CoachingToggle.defaultProps = {
|
|
||||||
phone_number: '',
|
|
||||||
error: '',
|
|
||||||
saveState: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
CoachingToggle.propTypes = {
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
error: PropTypes.string,
|
|
||||||
coaching: PropTypes.shape({
|
|
||||||
coaching_consent: PropTypes.bool.isRequired,
|
|
||||||
user: PropTypes.number.isRequired,
|
|
||||||
eligible_for_coaching: PropTypes.bool.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
|
|
||||||
saveSettings: PropTypes.func.isRequired,
|
|
||||||
saveMultipleSettings: PropTypes.func.isRequired,
|
|
||||||
updateDraft: PropTypes.func.isRequired,
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
phone_number: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(editableFieldSelector, {
|
|
||||||
saveSettings,
|
|
||||||
updateDraft,
|
|
||||||
saveMultipleSettings,
|
|
||||||
})(injectIntl(CoachingToggle));
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
'account.settings.field.phone_number': {
|
|
||||||
id: 'account.settings.field.phone_number',
|
|
||||||
defaultMessage: 'Phone Number',
|
|
||||||
description: 'The label for a phone numbers setting in the user profile',
|
|
||||||
},
|
|
||||||
'account.settings.field.phone_number.empty': {
|
|
||||||
id: 'account.settings.field.phone_number.empty',
|
|
||||||
defaultMessage: 'Add a phone number',
|
|
||||||
description: 'placeholder for a profiles empty phone number field',
|
|
||||||
},
|
|
||||||
'account.settings.field.coaching_consent': {
|
|
||||||
id: 'account.settings.field.coaching_consent',
|
|
||||||
defaultMessage: 'Coaching consent',
|
|
||||||
description: 'The label for the coaching consent setting in the user profile',
|
|
||||||
},
|
|
||||||
'account.settings.field.coaching_consent.tooltip': {
|
|
||||||
id: 'account.settings.field.coaching_consent.tooltip',
|
|
||||||
defaultMessage: 'MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text ‘STOP’ at anytime to opt-out of messages.',
|
|
||||||
description: 'A tooltip explaining what coaching is and who it is for',
|
|
||||||
},
|
|
||||||
'account.settings.field.coaching_consent.error': {
|
|
||||||
id: 'account.settings.field.coaching_consent.error',
|
|
||||||
defaultMessage: 'A valid US phone number is required to opt into coaching',
|
|
||||||
description: 'An error message that displays when a user attempts to consent to coaching without first providing a phone number in their profile',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default messages;
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import get from 'lodash.get';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get all settings related to the coaching plugin. Settings used
|
|
||||||
* by Microbachelors students.
|
|
||||||
* @param {Number} userId users are identified in the api by LMS id
|
|
||||||
*/
|
|
||||||
export async function getCoachingPreferences(userId) {
|
|
||||||
let data = {};
|
|
||||||
try {
|
|
||||||
({ data } = await getAuthenticatedHttpClient()
|
|
||||||
.get(`${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`));
|
|
||||||
} catch (error) {
|
|
||||||
// If a user isn't active the API call will fail with a lack of credentials.
|
|
||||||
data = {
|
|
||||||
coaching_consent: false,
|
|
||||||
user: userId,
|
|
||||||
eligible_for_coaching: false,
|
|
||||||
consent_form_seen: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* patch all of the settings related to coaching.
|
|
||||||
* @param {Number} userId users are identified in the api by LMS id
|
|
||||||
* @param {Object} commitValues { coaching }
|
|
||||||
*/
|
|
||||||
export async function patchCoachingPreferences(userId, commitValues) {
|
|
||||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`;
|
|
||||||
const { coaching } = commitValues;
|
|
||||||
coaching.user = userId;
|
|
||||||
|
|
||||||
await getAuthenticatedHttpClient()
|
|
||||||
.patch(requestUrl, coaching)
|
|
||||||
.catch((error) => {
|
|
||||||
const apiError = Object.create(error);
|
|
||||||
apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
|
|
||||||
if (get(apiError, 'fieldErrors.phone_number')) {
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
apiError.fieldErrors.coaching = apiError.fieldErrors.phone_number[0];
|
|
||||||
delete apiError.fieldErrors.phone_number;
|
|
||||||
}
|
|
||||||
throw apiError;
|
|
||||||
});
|
|
||||||
return commitValues;
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/* 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`CoachingConsent disables name field on enterprise user 1`] = `
|
|
||||||
<main>
|
|
||||||
<div
|
|
||||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Logo"
|
|
||||||
className="logo"
|
|
||||||
src="icon/mock/path"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
|
||||||
>
|
|
||||||
<h2
|
|
||||||
className="h2"
|
|
||||||
>
|
|
||||||
Let’s get started.
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<form
|
|
||||||
onSubmit={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="py-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="alert d-flex align-items-start alert alert-primary"
|
|
||||||
>
|
|
||||||
<div />
|
|
||||||
<div>
|
|
||||||
Your name is managed by
|
|
||||||
<b>
|
|
||||||
test person
|
|
||||||
</b>
|
|
||||||
. Contact your administrator for help.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
className="h6"
|
|
||||||
htmlFor="fullName"
|
|
||||||
>
|
|
||||||
Please confirm your name
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-decorator-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="has-value form-control"
|
|
||||||
defaultValue="edx edx"
|
|
||||||
disabled={true}
|
|
||||||
id="fullName"
|
|
||||||
name="full-name"
|
|
||||||
onBlur={[Function]}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="py-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
className="h6"
|
|
||||||
htmlFor="phoneNumber"
|
|
||||||
>
|
|
||||||
Enter your mobile number
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-decorator-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="has-value form-control"
|
|
||||||
defaultValue="1234567890"
|
|
||||||
id="phoneNumber"
|
|
||||||
name="phone_number"
|
|
||||||
onBlur={[Function]}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className=" py-3"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="small font-italic"
|
|
||||||
>
|
|
||||||
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="d-flex flex-column align-items-center"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="w-100 btn btn-outline-primary"
|
|
||||||
disabled={false}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Sign up for coaching
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="mt-3"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
|
||||||
href="http://localhost:18000/dashboard/"
|
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
I prefer not to be contacted with free coaching services
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CoachingConsent display completed box when successfully submitted 1`] = `
|
|
||||||
<main>
|
|
||||||
<div
|
|
||||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Logo"
|
|
||||||
className="logo"
|
|
||||||
src="icon/mock/path"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
className="d-flex justify-content-center align-items-center flex-column"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"height": "50vh",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="spinner-border text-primary"
|
|
||||||
role="status"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Submitting...
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`CoachingConsent should render 1`] = `
|
|
||||||
<main>
|
|
||||||
<div
|
|
||||||
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Logo"
|
|
||||||
className="logo"
|
|
||||||
src="icon/mock/path"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
|
|
||||||
>
|
|
||||||
<h2
|
|
||||||
className="h2"
|
|
||||||
>
|
|
||||||
Let’s get started.
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If you’re interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
|
|
||||||
</p>
|
|
||||||
<div>
|
|
||||||
<form
|
|
||||||
onSubmit={[Function]}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="py-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
className="h6"
|
|
||||||
htmlFor="fullName"
|
|
||||||
>
|
|
||||||
Please confirm your name
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-decorator-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="has-value form-control"
|
|
||||||
defaultValue="edx edx"
|
|
||||||
disabled={false}
|
|
||||||
id="fullName"
|
|
||||||
name="full-name"
|
|
||||||
onBlur={[Function]}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="py-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<label
|
|
||||||
className="h6"
|
|
||||||
htmlFor="phoneNumber"
|
|
||||||
>
|
|
||||||
Enter your mobile number
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
className="pgn__form-control-decorator-group"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
className="has-value form-control"
|
|
||||||
defaultValue="1234567890"
|
|
||||||
id="phoneNumber"
|
|
||||||
name="phone_number"
|
|
||||||
onBlur={[Function]}
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className=" py-3"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="small font-italic"
|
|
||||||
>
|
|
||||||
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="alert-warning mb-2"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="d-flex flex-column align-items-center"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className="w-100 btn btn-outline-primary"
|
|
||||||
disabled={false}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Sign up for coaching
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="mt-3"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
|
|
||||||
href="http://localhost:18000/dashboard/"
|
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
I prefer not to be contacted with free coaching services
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
`;
|
|
||||||
@@ -27,6 +27,7 @@ export const fetchSettingsSuccess = ({
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
|
countriesCodesList,
|
||||||
}) => ({
|
}) => ({
|
||||||
type: FETCH_SETTINGS.SUCCESS,
|
type: FETCH_SETTINGS.SUCCESS,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -35,6 +36,7 @@ export const fetchSettingsSuccess = ({
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
|
countriesCodesList,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,9 +79,9 @@ export const beginNameChange = (formId) => ({
|
|||||||
});
|
});
|
||||||
// SAVE SETTINGS ACTIONS
|
// SAVE SETTINGS ACTIONS
|
||||||
|
|
||||||
export const saveSettings = (formId, commitValues) => ({
|
export const saveSettings = (formId, commitValues, extendedProfile = {}) => ({
|
||||||
type: SAVE_SETTINGS.BASE,
|
type: SAVE_SETTINGS.BASE,
|
||||||
payload: { formId, commitValues },
|
payload: { formId, commitValues, extendedProfile },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const saveSettingsBegin = () => ({
|
export const saveSettingsBegin = () => ({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const EDUCATION_LEVELS = [
|
|||||||
'jhs',
|
'jhs',
|
||||||
'el',
|
'el',
|
||||||
'none',
|
'none',
|
||||||
'o',
|
'other',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GENDER_OPTIONS = [
|
export const GENDER_OPTIONS = [
|
||||||
@@ -34,6 +34,21 @@ export const GENDER_OPTIONS = [
|
|||||||
'm',
|
'm',
|
||||||
'o',
|
'o',
|
||||||
];
|
];
|
||||||
|
export const WORK_EXPERIENCE_OPTIONS = [
|
||||||
|
'',
|
||||||
|
'0',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'3',
|
||||||
|
'4',
|
||||||
|
'5',
|
||||||
|
'6',
|
||||||
|
'7',
|
||||||
|
'8',
|
||||||
|
'9',
|
||||||
|
'10+',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
export const COUNTRY_WITH_STATES = 'US';
|
export const COUNTRY_WITH_STATES = 'US';
|
||||||
|
|
||||||
@@ -117,6 +132,6 @@ export function getStatesList(country) {
|
|||||||
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
|
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DECLINED = 'declined';
|
export const FIELD_LABELS = {
|
||||||
export const SELF_DESCRIBE = 'self-describe';
|
COUNTRY: 'country',
|
||||||
export const OTHER = 'other';
|
};
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const defaultState = {
|
|||||||
verifiedName: null,
|
verifiedName: null,
|
||||||
mostRecentVerifiedName: {},
|
mostRecentVerifiedName: {},
|
||||||
verifiedNameHistory: {},
|
verifiedNameHistory: {},
|
||||||
|
countriesCodesList: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = (state = defaultState, action = {}) => {
|
const reducer = (state = defaultState, action = {}) => {
|
||||||
@@ -64,6 +65,7 @@ const reducer = (state = defaultState, action = {}) => {
|
|||||||
loaded: true,
|
loaded: true,
|
||||||
loadingError: null,
|
loadingError: null,
|
||||||
verifiedNameHistory: action.payload.verifiedNameHistory,
|
verifiedNameHistory: action.payload.verifiedNameHistory,
|
||||||
|
countriesCodesList: action.payload.countriesCodesList,
|
||||||
};
|
};
|
||||||
case FETCH_SETTINGS.FAILURE:
|
case FETCH_SETTINGS.FAILURE:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function* handleFetchSettings() {
|
|||||||
const { username, userId, roles: userRoles } = getAuthenticatedUser();
|
const { username, userId, roles: userRoles } = getAuthenticatedUser();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
thirdPartyAuthProviders, profileDataManager, timeZones, ...values
|
thirdPartyAuthProviders, profileDataManager, timeZones, countries, ...values
|
||||||
} = yield call(
|
} = yield call(
|
||||||
getSettings,
|
getSettings,
|
||||||
username,
|
username,
|
||||||
@@ -71,6 +71,7 @@ export function* handleFetchSettings() {
|
|||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
|
countriesCodesList: countries,
|
||||||
}));
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(fetchSettingsFailure(e.message));
|
yield put(fetchSettingsFailure(e.message));
|
||||||
@@ -83,8 +84,8 @@ export function* handleSaveSettings(action) {
|
|||||||
yield put(saveSettingsBegin());
|
yield put(saveSettingsBegin());
|
||||||
|
|
||||||
const { username, userId } = getAuthenticatedUser();
|
const { username, userId } = getAuthenticatedUser();
|
||||||
const { commitValues, formId } = action.payload;
|
const { commitValues, formId, extendedProfile } = action.payload;
|
||||||
const commitData = { [formId]: commitValues };
|
const commitData = Object.keys(extendedProfile).length > 0 ? extendedProfile : { [formId]: commitValues };
|
||||||
let savedValues = null;
|
let savedValues = null;
|
||||||
if (formId === 'siteLanguage') {
|
if (formId === 'siteLanguage') {
|
||||||
const previousSiteLanguage = getLocale();
|
const previousSiteLanguage = getLocale();
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ const previousSiteLanguageSelector = createSelector(
|
|||||||
accountSettings => accountSettings.previousSiteLanguage,
|
accountSettings => accountSettings.previousSiteLanguage,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const countriesSelector = createSelector(
|
||||||
|
accountSettingsSelector,
|
||||||
|
accountSettings => accountSettings.countriesCodesList,
|
||||||
|
);
|
||||||
|
|
||||||
const editableFieldErrorSelector = createSelector(
|
const editableFieldErrorSelector = createSelector(
|
||||||
editableFieldNameSelector,
|
editableFieldNameSelector,
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
@@ -106,11 +111,6 @@ const isEditingSelector = createSelector(
|
|||||||
(name, accountSettings) => accountSettings.openFormId === name,
|
(name, accountSettings) => accountSettings.openFormId === name,
|
||||||
);
|
);
|
||||||
|
|
||||||
const confirmationValuesSelector = createSelector(
|
|
||||||
accountSettingsSelector,
|
|
||||||
accountSettings => accountSettings.confirmationValues,
|
|
||||||
);
|
|
||||||
|
|
||||||
const errorSelector = createSelector(
|
const errorSelector = createSelector(
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
accountSettings => accountSettings.errors,
|
accountSettings => accountSettings.errors,
|
||||||
@@ -161,7 +161,7 @@ function chooseFormValue(draft, committed) {
|
|||||||
return draft !== undefined ? draft : committed;
|
return draft !== undefined ? draft : committed;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formValuesSelector = createSelector(
|
export const formValuesSelector = createSelector(
|
||||||
valuesSelector,
|
valuesSelector,
|
||||||
draftsSelector,
|
draftsSelector,
|
||||||
(values, drafts) => {
|
(values, drafts) => {
|
||||||
@@ -169,6 +169,20 @@ const formValuesSelector = createSelector(
|
|||||||
Object.entries(values).forEach(([name, value]) => {
|
Object.entries(values).forEach(([name, value]) => {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
formValues[name] = chooseFormValue(drafts[name], value);
|
formValues[name] = chooseFormValue(drafts[name], value);
|
||||||
|
} else if (typeof value === 'object' && name === 'extended_profile' && value !== null) {
|
||||||
|
const extendedProfile = value.slice();
|
||||||
|
const draftsKeys = Object.keys(drafts);
|
||||||
|
|
||||||
|
if (draftsKeys.length !== 0) {
|
||||||
|
const draftFieldName = draftsKeys[0];
|
||||||
|
const index = extendedProfile.findIndex((profile) => profile.field_name === draftFieldName);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
extendedProfile[index] = { field_name: draftFieldName, field_value: drafts[draftFieldName] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formValues.extended_profile = [...extendedProfile];
|
||||||
} else {
|
} else {
|
||||||
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
formValues[name] = chooseFormValue(drafts[name], value) || '';
|
||||||
}
|
}
|
||||||
@@ -228,6 +242,7 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
mostRecentApprovedVerifiedNameValueSelector,
|
mostRecentApprovedVerifiedNameValueSelector,
|
||||||
mostRecentVerifiedNameSelector,
|
mostRecentVerifiedNameSelector,
|
||||||
sortedVerifiedNameHistorySelector,
|
sortedVerifiedNameHistorySelector,
|
||||||
|
countriesSelector,
|
||||||
(
|
(
|
||||||
accountSettings,
|
accountSettings,
|
||||||
siteLanguageOptions,
|
siteLanguageOptions,
|
||||||
@@ -245,6 +260,7 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
verifiedName,
|
verifiedName,
|
||||||
mostRecentVerifiedName,
|
mostRecentVerifiedName,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
|
countriesCodesList,
|
||||||
) => ({
|
) => ({
|
||||||
siteLanguageOptions,
|
siteLanguageOptions,
|
||||||
siteLanguage,
|
siteLanguage,
|
||||||
@@ -265,6 +281,7 @@ export const accountSettingsPageSelector = createSelector(
|
|||||||
verifiedName,
|
verifiedName,
|
||||||
mostRecentVerifiedName,
|
mostRecentVerifiedName,
|
||||||
verifiedNameHistory,
|
verifiedNameHistory,
|
||||||
|
countriesCodesList,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -289,50 +306,6 @@ export const certPreferenceSelector = createSelector(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const coachingConsentPageSelector = createSelector(
|
|
||||||
accountSettingsSelector,
|
|
||||||
formValuesSelector,
|
|
||||||
activeAccountSelector,
|
|
||||||
profileDataManagerSelector,
|
|
||||||
saveStateSelector,
|
|
||||||
confirmationValuesSelector,
|
|
||||||
errorSelector,
|
|
||||||
(
|
|
||||||
accountSettings,
|
|
||||||
formValues,
|
|
||||||
activeAccount,
|
|
||||||
profileDataManager,
|
|
||||||
saveState,
|
|
||||||
confirmationValues,
|
|
||||||
errors,
|
|
||||||
) => ({
|
|
||||||
loading: accountSettings.loading,
|
|
||||||
loaded: accountSettings.loaded,
|
|
||||||
loadingError: accountSettings.loadingError,
|
|
||||||
isActive: activeAccount,
|
|
||||||
profileDataManager,
|
|
||||||
formValues,
|
|
||||||
saveState,
|
|
||||||
confirmationValues,
|
|
||||||
formErrors: errors,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const demographicsSectionSelector = createSelector(
|
|
||||||
formValuesSelector,
|
|
||||||
draftsSelector,
|
|
||||||
errorSelector,
|
|
||||||
(
|
|
||||||
formValues,
|
|
||||||
drafts,
|
|
||||||
errors,
|
|
||||||
) => ({
|
|
||||||
formValues,
|
|
||||||
drafts,
|
|
||||||
formErrors: errors,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const nameChangeSelector = createSelector(
|
export const nameChangeSelector = createSelector(
|
||||||
accountSettingsSelector,
|
accountSettingsSelector,
|
||||||
formValuesSelector,
|
formValuesSelector,
|
||||||
|
|||||||
72
src/account-settings/data/selectors.test.js
Normal file
72
src/account-settings/data/selectors.test.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { profileDataManagerSelector, formValuesSelector } from './selectors';
|
||||||
|
|
||||||
|
const testValue = 'test VALUE';
|
||||||
|
|
||||||
|
describe('profileDataManagerSelector', () => {
|
||||||
|
it('returns the profileDataManager from the state', () => {
|
||||||
|
const state = {
|
||||||
|
accountSettings: {
|
||||||
|
profileDataManager: { testValue },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = profileDataManagerSelector(state);
|
||||||
|
|
||||||
|
expect(result).toEqual(state.accountSettings.profileDataManager);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly select form values', () => {
|
||||||
|
const state = {
|
||||||
|
accountSettings: {
|
||||||
|
values: {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 25,
|
||||||
|
},
|
||||||
|
drafts: {
|
||||||
|
age: 26,
|
||||||
|
|
||||||
|
},
|
||||||
|
verifiedNameHistory: 'test',
|
||||||
|
confirmationValues: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = formValuesSelector(state);
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
name: 'John Doe',
|
||||||
|
age: 26,
|
||||||
|
verified_name: '',
|
||||||
|
useVerifiedNameForCerts: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should correctly select form values with extended_profile', () => {
|
||||||
|
// Mock data with extended_profile field in both values and drafts
|
||||||
|
const state = {
|
||||||
|
accountSettings: {
|
||||||
|
values: {
|
||||||
|
extended_profile: [
|
||||||
|
{ field_name: 'test_field', field_value: '5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
drafts: { test_field: '6' },
|
||||||
|
verifiedNameHistory: 'test',
|
||||||
|
confirmationValues: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = formValuesSelector(state);
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
verified_name: '',
|
||||||
|
useVerifiedNameForCerts: false,
|
||||||
|
extended_profile: [ // Draft value should override the committed value
|
||||||
|
{ field_name: 'test_field', field_value: '6' }, // Value from the committed values
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(result).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import pickBy from 'lodash.pickby';
|
|
||||||
import omit from 'lodash.omit';
|
import omit from 'lodash.omit';
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty';
|
||||||
|
|
||||||
import { handleRequestError, unpackFieldErrors } from './utils';
|
import { handleRequestError, unpackFieldErrors } from './utils';
|
||||||
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
||||||
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
|
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
|
||||||
import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service';
|
import { FIELD_LABELS } from './constants';
|
||||||
import { getDemographics, getDemographicsOptions, patchDemographics } from '../demographics/data/service';
|
|
||||||
import { DEMOGRAPHICS_FIELDS } from '../demographics/data/utils';
|
|
||||||
|
|
||||||
const SOCIAL_PLATFORMS = [
|
const SOCIAL_PLATFORMS = [
|
||||||
{ id: 'twitter', key: 'social_link_twitter' },
|
{ id: 'xTwitter', key: 'social_link_x' },
|
||||||
{ id: 'facebook', key: 'social_link_facebook' },
|
{ id: 'facebook', key: 'social_link_facebook' },
|
||||||
{ id: 'linkedin', key: 'social_link_linkedin' },
|
{ id: 'linkedin', key: 'social_link_linkedin' },
|
||||||
];
|
];
|
||||||
@@ -155,28 +153,6 @@ export async function getProfileDataManager(username, userRoles) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A function to determine if the Demographics questions should be displayed to the user. For the
|
|
||||||
* MVP release of Demographics we are limiting the Demographics question visibility only to
|
|
||||||
* MicroBachelors learners.
|
|
||||||
*/
|
|
||||||
export async function shouldDisplayDemographicsQuestions() {
|
|
||||||
const requestUrl = `${getConfig().LMS_BASE_URL}/api/demographics/v1/demographics/status/`;
|
|
||||||
let data = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
({ data } = await getAuthenticatedHttpClient().get(requestUrl));
|
|
||||||
if (data.display) {
|
|
||||||
return data.display;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// if there was an error then we just hide the section
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getVerifiedName() {
|
export async function getVerifiedName() {
|
||||||
let data;
|
let data;
|
||||||
const client = getAuthenticatedHttpClient();
|
const client = getAuthenticatedHttpClient();
|
||||||
@@ -212,31 +188,43 @@ export async function postVerifiedName(data) {
|
|||||||
.catch(error => handleRequestError(error));
|
.catch(error => handleRequestError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractCountryList(data) {
|
||||||
|
return data?.fields
|
||||||
|
.find(({ name }) => name === FIELD_LABELS.COUNTRY)
|
||||||
|
?.options?.map(({ value }) => (value)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCountryList() {
|
||||||
|
const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await getAuthenticatedHttpClient().get(url);
|
||||||
|
return extractCountryList(data);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single function to GET everything considered a setting.
|
* A single function to GET everything considered a setting. Currently encapsulates Account, Preferences, and
|
||||||
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
|
* ThirdPartyAuth.
|
||||||
*/
|
*/
|
||||||
export async function getSettings(username, userRoles, userId) {
|
export async function getSettings(username, userRoles) {
|
||||||
const [
|
const [
|
||||||
account,
|
account,
|
||||||
preferences,
|
preferences,
|
||||||
thirdPartyAuthProviders,
|
thirdPartyAuthProviders,
|
||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
coaching,
|
countries,
|
||||||
shouldDisplayDemographicsQuestionsResponse,
|
|
||||||
demographics,
|
|
||||||
demographicsOptions,
|
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getAccount(username),
|
getAccount(username),
|
||||||
getPreferences(username),
|
getPreferences(username),
|
||||||
getThirdPartyAuthProviders(),
|
getThirdPartyAuthProviders(),
|
||||||
getProfileDataManager(username, userRoles),
|
getProfileDataManager(username, userRoles),
|
||||||
getTimeZones(),
|
getTimeZones(),
|
||||||
getConfig().COACHING_ENABLED && getCoachingPreferences(userId),
|
getCountryList(),
|
||||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && shouldDisplayDemographicsQuestions(),
|
|
||||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
|
|
||||||
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographicsOptions(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -245,36 +233,26 @@ export async function getSettings(username, userRoles, userId) {
|
|||||||
thirdPartyAuthProviders,
|
thirdPartyAuthProviders,
|
||||||
profileDataManager,
|
profileDataManager,
|
||||||
timeZones,
|
timeZones,
|
||||||
coaching,
|
countries,
|
||||||
shouldDisplayDemographicsSection: shouldDisplayDemographicsQuestionsResponse,
|
|
||||||
...demographics,
|
|
||||||
demographicsOptions,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A single function to PATCH everything considered a setting.
|
* A single function to PATCH everything considered a setting.
|
||||||
* Currently encapsulates Account, Preferences, coaching and ThirdPartyAuth
|
* Currently encapsulates Account, Preferences, ThirdPartyAuth
|
||||||
*/
|
*/
|
||||||
export async function patchSettings(username, commitValues, userId) {
|
export async function patchSettings(username, commitValues) {
|
||||||
// Note: time_zone exists in the return value from user/v1/accounts
|
// Note: time_zone exists in the return value from user/v1/accounts
|
||||||
// but it is always null and won't update. It also exists in
|
// but it is always null and won't update. It also exists in
|
||||||
// user/v1/preferences where it does update. This is the one we use.
|
// user/v1/preferences where it does update. This is the one we use.
|
||||||
const preferenceKeys = ['time_zone'];
|
const preferenceKeys = ['time_zone'];
|
||||||
const coachingKeys = ['coaching'];
|
|
||||||
const demographicsKeys = DEMOGRAPHICS_FIELDS;
|
|
||||||
const certificateKeys = ['useVerifiedNameForCerts'];
|
const certificateKeys = ['useVerifiedNameForCerts'];
|
||||||
const isDemographicsKey = (value, key) => key.includes('demographics');
|
|
||||||
const accountCommitValues = omit(
|
const accountCommitValues = omit(
|
||||||
commitValues,
|
commitValues,
|
||||||
preferenceKeys,
|
preferenceKeys,
|
||||||
coachingKeys,
|
|
||||||
demographicsKeys,
|
|
||||||
certificateKeys,
|
certificateKeys,
|
||||||
);
|
);
|
||||||
const preferenceCommitValues = pick(commitValues, preferenceKeys);
|
const preferenceCommitValues = pick(commitValues, preferenceKeys);
|
||||||
const coachingCommitValues = pick(commitValues, coachingKeys);
|
|
||||||
const demographicsCommitValues = pickBy(commitValues, isDemographicsKey);
|
|
||||||
const certCommitValues = pick(commitValues, certificateKeys);
|
const certCommitValues = pick(commitValues, certificateKeys);
|
||||||
const patchRequests = [];
|
const patchRequests = [];
|
||||||
|
|
||||||
@@ -284,12 +262,6 @@ export async function patchSettings(username, commitValues, userId) {
|
|||||||
if (!isEmpty(preferenceCommitValues)) {
|
if (!isEmpty(preferenceCommitValues)) {
|
||||||
patchRequests.push(patchPreferences(username, preferenceCommitValues));
|
patchRequests.push(patchPreferences(username, preferenceCommitValues));
|
||||||
}
|
}
|
||||||
if (!isEmpty(coachingCommitValues)) {
|
|
||||||
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
|
|
||||||
}
|
|
||||||
if (!isEmpty(demographicsCommitValues)) {
|
|
||||||
patchRequests.push(patchDemographics(userId, demographicsCommitValues));
|
|
||||||
}
|
|
||||||
if (!isEmpty(certCommitValues)) {
|
if (!isEmpty(certCommitValues)) {
|
||||||
patchRequests.push(postVerifiedNameConfig(username, certCommitValues));
|
patchRequests.push(postVerifiedNameConfig(username, certCommitValues));
|
||||||
}
|
}
|
||||||
|
|||||||
181
src/account-settings/data/service.test.js
Normal file
181
src/account-settings/data/service.test.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
|
import { FIELD_LABELS } from './constants';
|
||||||
|
import {
|
||||||
|
getAccount,
|
||||||
|
patchAccount,
|
||||||
|
getPreferences,
|
||||||
|
patchPreferences,
|
||||||
|
getTimeZones,
|
||||||
|
getProfileDataManager,
|
||||||
|
getVerifiedName,
|
||||||
|
getVerifiedNameHistory,
|
||||||
|
postVerifiedName,
|
||||||
|
getCountryList,
|
||||||
|
patchSettings,
|
||||||
|
} from './service';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('@edx/frontend-platform/logging');
|
||||||
|
|
||||||
|
const mockHttpClient = {
|
||||||
|
get: jest.fn(),
|
||||||
|
patch: jest.fn(),
|
||||||
|
post: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue(mockHttpClient);
|
||||||
|
getConfig.mockReturnValue({ LMS_BASE_URL: 'http://lms.test' });
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('account service', () => {
|
||||||
|
describe('getAccount', () => {
|
||||||
|
it('returns unpacked account data', async () => {
|
||||||
|
const apiResponse = {
|
||||||
|
username: 'testuser',
|
||||||
|
social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
|
||||||
|
language_proficiencies: [{ code: 'en' }],
|
||||||
|
};
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: apiResponse });
|
||||||
|
|
||||||
|
const result = await getAccount('testuser');
|
||||||
|
expect(mockHttpClient.get).toHaveBeenCalledWith('http://lms.test/api/user/v1/accounts/testuser');
|
||||||
|
expect(result.social_link_x).toEqual('http://t');
|
||||||
|
expect(result.language_proficiencies).toEqual('en');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('patchAccount', () => {
|
||||||
|
it('sends packed commit data and returns unpacked response', async () => {
|
||||||
|
const commit = { social_link_x: 'http://t' };
|
||||||
|
const apiResponse = {
|
||||||
|
username: 'testuser',
|
||||||
|
social_links: [{ platform: 'xTwitter', social_link: 'http://t' }],
|
||||||
|
language_proficiencies: [],
|
||||||
|
};
|
||||||
|
mockHttpClient.patch.mockResolvedValue({ data: apiResponse });
|
||||||
|
|
||||||
|
const result = await patchAccount('testuser', commit);
|
||||||
|
expect(mockHttpClient.patch).toHaveBeenCalledWith(
|
||||||
|
'http://lms.test/api/user/v1/accounts/testuser',
|
||||||
|
expect.objectContaining({ social_links: [{ platform: 'xTwitter', social_link: 'http://t' }] }),
|
||||||
|
expect.any(Object),
|
||||||
|
);
|
||||||
|
expect(result.social_link_x).toEqual('http://t');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPreferences', () => {
|
||||||
|
it('returns preferences data', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: { theme: 'dark' } });
|
||||||
|
const result = await getPreferences('user');
|
||||||
|
expect(result.theme).toBe('dark');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('patchPreferences', () => {
|
||||||
|
it('patches preferences and returns commitValues', async () => {
|
||||||
|
mockHttpClient.patch.mockResolvedValue({});
|
||||||
|
const commit = { time_zone: 'UTC' };
|
||||||
|
const result = await patchPreferences('user', commit);
|
||||||
|
expect(mockHttpClient.patch).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(commit);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTimeZones', () => {
|
||||||
|
it('returns data from API', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: ['UTC', 'PST'] });
|
||||||
|
const result = await getTimeZones('PK');
|
||||||
|
expect(mockHttpClient.get).toHaveBeenCalledWith(
|
||||||
|
'http://lms.test/user_api/v1/preferences/time_zones/',
|
||||||
|
{ params: { country_code: 'PK' } },
|
||||||
|
);
|
||||||
|
expect(result).toEqual(['UTC', 'PST']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getProfileDataManager', () => {
|
||||||
|
it('returns null if no enterprise manages profile', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: { results: [] } });
|
||||||
|
const result = await getProfileDataManager('user', ['learner']);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns enterprise name if sync is enabled', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: { results: [{ enterprise_customer: { name: 'Acme', sync_learner_profile_data: true } }] } });
|
||||||
|
const result = await getProfileDataManager('user', ['enterprise_learner']);
|
||||||
|
expect(result).toBe('Acme');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getVerifiedName', () => {
|
||||||
|
it('returns verified name data', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: { verified: true } });
|
||||||
|
const result = await getVerifiedName();
|
||||||
|
expect(result.verified).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns {} on error', async () => {
|
||||||
|
mockHttpClient.get.mockRejectedValue(new Error('fail'));
|
||||||
|
const result = await getVerifiedName();
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getVerifiedNameHistory', () => {
|
||||||
|
it('returns verified name history data', async () => {
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: [{ id: 1 }] });
|
||||||
|
const result = await getVerifiedNameHistory();
|
||||||
|
expect(result[0].id).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('postVerifiedName', () => {
|
||||||
|
it('posts verified name data', async () => {
|
||||||
|
mockHttpClient.post.mockResolvedValue({});
|
||||||
|
await postVerifiedName({ first_name: 'A' });
|
||||||
|
expect(mockHttpClient.post).toHaveBeenCalledWith(
|
||||||
|
'http://lms.test/api/edx_name_affirmation/v1/verified_name',
|
||||||
|
{ first_name: 'A' },
|
||||||
|
{ headers: { Accept: 'application/json' } },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCountryList', () => {
|
||||||
|
it('extracts country values from registration API', async () => {
|
||||||
|
const apiResponse = { fields: [{ name: FIELD_LABELS.COUNTRY, options: [{ value: 'PK' }] }] };
|
||||||
|
mockHttpClient.get.mockResolvedValue({ data: apiResponse });
|
||||||
|
const result = await getCountryList();
|
||||||
|
expect(result).toEqual(['PK']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns [] and logs error on failure', async () => {
|
||||||
|
mockHttpClient.get.mockRejectedValue(new Error('fail'));
|
||||||
|
const result = await getCountryList();
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
expect(logError).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('patchSettings', () => {
|
||||||
|
it('calls patchAccount and patchPreferences as needed', async () => {
|
||||||
|
mockHttpClient.patch.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
username: 'user',
|
||||||
|
social_links: [],
|
||||||
|
language_proficiencies: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await patchSettings('user', { time_zone: 'UTC', social_link_twitter: 't' });
|
||||||
|
expect(result.username).toBe('user');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { put } from 'redux-saga/effects';
|
import { put } from 'redux-saga/effects';
|
||||||
import { logError } from '@edx/frontend-platform/logging';
|
import { logError } from '@edx/frontend-platform/logging';
|
||||||
import { history } from '@edx/frontend-platform';
|
|
||||||
|
|
||||||
export default function* handleFailure(error, failureAction = null, failureRedirectPath = null) {
|
export default function* handleFailure(error, navigate, failureAction = null, failureRedirectPath = null) {
|
||||||
if (error.fieldErrors && failureAction !== null) {
|
if (error.fieldErrors && failureAction !== null) {
|
||||||
yield put(failureAction({ fieldErrors: error.fieldErrors }));
|
yield put(failureAction({ fieldErrors: error.fieldErrors }));
|
||||||
}
|
}
|
||||||
@@ -11,6 +10,6 @@ export default function* handleFailure(error, failureAction = null, failureRedir
|
|||||||
yield put(failureAction(error.message));
|
yield put(failureAction(error.message));
|
||||||
}
|
}
|
||||||
if (failureRedirectPath !== null) {
|
if (failureRedirectPath !== null) {
|
||||||
history.push(failureRedirectPath);
|
navigate(failureRedirectPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Hyperlink } from '@edx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
@@ -13,7 +12,8 @@ import messages from './messages';
|
|||||||
import Alert from '../Alert';
|
import Alert from '../Alert';
|
||||||
|
|
||||||
const BeforeProceedingBanner = (props) => {
|
const BeforeProceedingBanner = (props) => {
|
||||||
const { instructionMessageId, intl, supportArticleUrl } = props;
|
const { instructionMessageId, supportArticleUrl } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -25,10 +25,12 @@ const BeforeProceedingBanner = (props) => {
|
|||||||
defaultMessage="Before proceeding, please {actionLink}."
|
defaultMessage="Before proceeding, please {actionLink}."
|
||||||
description="Error that appears if you are trying to delete your account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
description="Error that appears if you are trying to delete your account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
||||||
values={{
|
values={{
|
||||||
actionLink: (
|
actionLink: supportArticleUrl ? (
|
||||||
<Hyperlink destination={supportArticleUrl}>
|
<Hyperlink destination={supportArticleUrl}>
|
||||||
{intl.formatMessage(messages[instructionMessageId])}
|
{intl.formatMessage(messages[instructionMessageId])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
|
) : (
|
||||||
|
intl.formatMessage(messages[instructionMessageId])
|
||||||
),
|
),
|
||||||
siteName: getConfig().SITE_NAME,
|
siteName: getConfig().SITE_NAME,
|
||||||
}}
|
}}
|
||||||
@@ -39,8 +41,7 @@ const BeforeProceedingBanner = (props) => {
|
|||||||
|
|
||||||
BeforeProceedingBanner.propTypes = {
|
BeforeProceedingBanner.propTypes = {
|
||||||
instructionMessageId: PropTypes.string.isRequired,
|
instructionMessageId: PropTypes.string.isRequired,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
supportArticleUrl: PropTypes.string.isRequired,
|
supportArticleUrl: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(BeforeProceedingBanner);
|
export default BeforeProceedingBanner;
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
jest.mock('react-dom', () => ({
|
||||||
|
...jest.requireActual('react-dom'),
|
||||||
|
createPortal: jest.fn(node => node), // Mock portal behavior
|
||||||
|
}));
|
||||||
|
|
||||||
|
import BeforeProceedingBanner from './BeforeProceedingBanner'; // eslint-disable-line import/first
|
||||||
|
|
||||||
|
describe('BeforeProceedingBanner', () => {
|
||||||
|
it('should match the snapshot if SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT does not have a support link', () => {
|
||||||
|
const props = {
|
||||||
|
instructionMessageId: 'account.settings.delete.account.please.unlink',
|
||||||
|
supportArticleUrl: '',
|
||||||
|
};
|
||||||
|
const tree = renderer
|
||||||
|
.create((
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<BeforeProceedingBanner
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
))
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should match the snapshot when SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT has a support link', () => {
|
||||||
|
const props = {
|
||||||
|
instructionMessageId: 'account.settings.delete.account.please.unlink',
|
||||||
|
supportArticleUrl: 'http://test-support.edx',
|
||||||
|
};
|
||||||
|
const tree = renderer
|
||||||
|
.create((
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<BeforeProceedingBanner
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
))
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -3,8 +3,8 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
AlertModal,
|
AlertModal,
|
||||||
Button, Input, ValidationFormGroup, ActionRow,
|
Button, Form, ActionRow,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
@@ -78,10 +78,11 @@ export class ConfirmationModal extends Component {
|
|||||||
isOpen={open}
|
isOpen={open}
|
||||||
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
title={intl.formatMessage(messages['account.settings.delete.account.modal.header'])}
|
||||||
onClose={onCancel}
|
onClose={onCancel}
|
||||||
|
isOverflowVisible
|
||||||
footerNode={(
|
footerNode={(
|
||||||
<ActionRow>
|
<ActionRow>
|
||||||
<Button variant="link" onClick={onCancel}>Cancel</Button>
|
<Button variant="link" onClick={onCancel}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.cancel'])}</Button>
|
||||||
<Button variant="danger" onClick={onSubmit}>Yes, Delete</Button>
|
<Button variant="danger" onClick={onSubmit}>{intl.formatMessage(messages['account.settings.delete.account.modal.confirm.delete'])}</Button>
|
||||||
</ActionRow>
|
</ActionRow>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -107,22 +108,26 @@ export class ConfirmationModal extends Component {
|
|||||||
<PrintingInstructions />
|
<PrintingInstructions />
|
||||||
</p>
|
</p>
|
||||||
</Alert>
|
</Alert>
|
||||||
<ValidationFormGroup
|
<Form.Group
|
||||||
for={passwordFieldId}
|
for={passwordFieldId}
|
||||||
invalid={errorType !== null}
|
isInvalid={errorType !== null}
|
||||||
invalidMessage={intl.formatMessage(invalidMessage)}
|
|
||||||
>
|
>
|
||||||
<label className="d-block" htmlFor={passwordFieldId}>
|
<Form.Label className="d-block" htmlFor={passwordFieldId}>
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
{intl.formatMessage(messages['account.settings.delete.account.modal.enter.password'])}
|
||||||
</label>
|
</Form.Label>
|
||||||
<Input
|
<Form.Control
|
||||||
name="password"
|
name="password"
|
||||||
id={passwordFieldId}
|
id={passwordFieldId}
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</ValidationFormGroup>
|
{errorType !== null && (
|
||||||
|
<Form.Control.Feedback type="invalid" feedback-for={passwordFieldId}>
|
||||||
|
{intl.formatMessage(invalidMessage)}
|
||||||
|
</Form.Control.Feedback>
|
||||||
|
)}
|
||||||
|
</Form.Group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</AlertModal>
|
</AlertModal>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
||||||
ReactDOM.createPortal = node => node;
|
jest.mock('react-dom', () => ({
|
||||||
|
...jest.requireActual('react-dom'),
|
||||||
|
createPortal: jest.fn(node => node), // Mock portal behavior
|
||||||
|
}));
|
||||||
|
|
||||||
import { ConfirmationModal } from './ConfirmationModal'; // eslint-disable-line import/first
|
import { ConfirmationModal } from './ConfirmationModal'; // eslint-disable-line import/first
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Button, Hyperlink } from '@edx/paragon';
|
import { Button, Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import {
|
import {
|
||||||
@@ -59,6 +59,7 @@ export class DeleteAccount extends React.Component {
|
|||||||
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
hasLinkedTPA, isVerifiedAccount, status, errorType, intl,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
const canDelete = isVerifiedAccount && !hasLinkedTPA;
|
||||||
|
const supportArticleUrl = process.env.SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT;
|
||||||
|
|
||||||
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
|
// TODO: We lack a good way of providing custom language for a particular site. This is a hack
|
||||||
// to allow edx.org to fulfill its business requirements.
|
// to allow edx.org to fulfill its business requirements.
|
||||||
@@ -75,67 +76,74 @@ export class DeleteAccount extends React.Component {
|
|||||||
<h2 className="section-heading h4 mb-3">
|
<h2 className="section-heading h4 mb-3">
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
{intl.formatMessage(messages['account.settings.delete.account.header'])}
|
||||||
</h2>
|
</h2>
|
||||||
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
{
|
||||||
<p>
|
this.props.canDeleteAccount ? (
|
||||||
{intl.formatMessage(
|
<>
|
||||||
messages['account.settings.delete.account.text.1'],
|
<p>{intl.formatMessage(messages['account.settings.delete.account.subheader'])}</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p>
|
||||||
)}
|
{intl.formatMessage(
|
||||||
</p>
|
messages['account.settings.delete.account.text.1'],
|
||||||
<p>
|
{ siteName: getConfig().SITE_NAME },
|
||||||
{intl.formatMessage(
|
)}
|
||||||
messages[deleteAccountText2MessageKey],
|
</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p>
|
||||||
)}
|
{intl.formatMessage(
|
||||||
</p>
|
messages[deleteAccountText2MessageKey],
|
||||||
<p>
|
{ siteName: getConfig().SITE_NAME },
|
||||||
<PrintingInstructions />
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-danger h6">
|
<p>
|
||||||
{intl.formatMessage(
|
<PrintingInstructions />
|
||||||
messages['account.settings.delete.account.text.warning'],
|
</p>
|
||||||
{ siteName: getConfig().SITE_NAME },
|
<p className="text-danger h6">
|
||||||
)}
|
{intl.formatMessage(
|
||||||
</p>
|
messages['account.settings.delete.account.text.warning'],
|
||||||
<p>
|
{ siteName: getConfig().SITE_NAME },
|
||||||
<Hyperlink destination="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings">
|
)}
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
</p>
|
||||||
</Hyperlink>
|
<p>
|
||||||
</p>
|
<Hyperlink destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics">
|
||||||
<p>
|
{intl.formatMessage(messages['account.settings.delete.account.text.change.instead'])}
|
||||||
<Button
|
</Hyperlink>
|
||||||
variant="outline-danger"
|
</p>
|
||||||
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
<p>
|
||||||
disabled={!canDelete}
|
<Button
|
||||||
>
|
variant="outline-danger"
|
||||||
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
onClick={canDelete ? this.props.deleteAccountConfirmation : null}
|
||||||
</Button>
|
disabled={!canDelete}
|
||||||
</p>
|
>
|
||||||
|
{intl.formatMessage(messages['account.settings.delete.account.button'])}
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
{isVerifiedAccount ? null : (
|
||||||
|
<BeforeProceedingBanner
|
||||||
|
instructionMessageId={optInInstructionMessageId}
|
||||||
|
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{hasLinkedTPA ? (
|
||||||
|
<BeforeProceedingBanner
|
||||||
|
instructionMessageId="account.settings.delete.account.please.unlink"
|
||||||
|
supportArticleUrl={supportArticleUrl}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{isVerifiedAccount ? null : (
|
<ConnectedConfirmationModal
|
||||||
<BeforeProceedingBanner
|
status={status}
|
||||||
instructionMessageId={optInInstructionMessageId}
|
errorType={errorType}
|
||||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
|
onSubmit={this.handleSubmit}
|
||||||
/>
|
onCancel={this.handleCancel}
|
||||||
)}
|
onChange={this.handlePasswordChange}
|
||||||
|
password={this.state.password}
|
||||||
|
/>
|
||||||
|
|
||||||
{hasLinkedTPA ? (
|
<ConnectedSuccessModal status={status} onClose={this.handleFinalClose} />
|
||||||
<BeforeProceedingBanner
|
</>
|
||||||
instructionMessageId="account.settings.delete.account.please.unlink"
|
) : (
|
||||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
<p>{intl.formatMessage(messages['account.settings.cannot.delete.account.text'])}</p>
|
||||||
/>
|
)
|
||||||
) : null}
|
}
|
||||||
|
|
||||||
<ConnectedConfirmationModal
|
|
||||||
status={status}
|
|
||||||
errorType={errorType}
|
|
||||||
onSubmit={this.handleSubmit}
|
|
||||||
onCancel={this.handleCancel}
|
|
||||||
onChange={this.handlePasswordChange}
|
|
||||||
password={this.state.password}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ConnectedSuccessModal status={status} onClose={this.handleFinalClose} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -151,6 +159,7 @@ DeleteAccount.propTypes = {
|
|||||||
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
||||||
hasLinkedTPA: PropTypes.bool,
|
hasLinkedTPA: PropTypes.bool,
|
||||||
isVerifiedAccount: PropTypes.bool,
|
isVerifiedAccount: PropTypes.bool,
|
||||||
|
canDeleteAccount: PropTypes.bool,
|
||||||
intl: intlShape.isRequired,
|
intl: intlShape.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,6 +168,7 @@ DeleteAccount.defaultProps = {
|
|||||||
isVerifiedAccount: true,
|
isVerifiedAccount: true,
|
||||||
status: null,
|
status: null,
|
||||||
errorType: null,
|
errorType: null,
|
||||||
|
canDeleteAccount: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Assume we're part of the accountSettings state.
|
// Assume we're part of the accountSettings state.
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import renderer from 'react-test-renderer';
|
|||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Testing the modals separately, they just clutter up the snapshots if included here.
|
// Testing the modals separately, they just clutter up the snapshots if included here.
|
||||||
jest.mock('./ConfirmationModal', () => function () {
|
jest.mock('./ConfirmationModal', () => function ConfirmationModalMock() {
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
jest.mock('./SuccessModal', () => function () {
|
jest.mock('./SuccessModal', () => function SuccessModalMock() {
|
||||||
return <></>;
|
return <></>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import React from 'react';
|
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
import { Hyperlink } from '@edx/paragon';
|
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
const PrintingInstructions = (props) => {
|
const PrintingInstructions = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const actionLink = (
|
const actionLink = (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
// TODO: What would a generic version of this link look like? Should
|
// TODO: What would a generic version of this link look like? Should
|
||||||
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
|
// CERTIFICATE_SHARING_HELP_URL really be a configuration variable? In the meantime,
|
||||||
// We've removed the link from the default message.
|
// We've removed the link from the default message.
|
||||||
destination="https://support.edx.org/hc/en-us/sections/115004173027-Receive-and-Share-edX-Certificates"
|
destination="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UVVOA2/certificates"
|
||||||
>
|
>
|
||||||
{props.intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
|
{intl.formatMessage(messages['account.settings.delete.account.text.3.link'])}
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -40,8 +40,4 @@ const PrintingInstructions = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintingInstructions.propTypes = {
|
export default PrintingInstructions;
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(PrintingInstructions);
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import { ModalLayer, ModalCloseButton } from '@edx/paragon';
|
import { ModalLayer, ModalCloseButton } from '@openedx/paragon';
|
||||||
|
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
|
|
||||||
export const SuccessModal = (props) => {
|
export const SuccessModal = (props) => {
|
||||||
const { status, intl, onClose } = props;
|
const intl = useIntl();
|
||||||
|
const { status, onClose } = props;
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<ModalLayer isOpen={status === 'deleted'} onClose={onClose}>
|
<ModalLayer isOpen={status === 'deleted'} onClose={onClose}>
|
||||||
@@ -20,7 +20,7 @@ export const SuccessModal = (props) => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
<ModalCloseButton className="float-right" variant="link">Close</ModalCloseButton>
|
<ModalCloseButton className="float-right" variant="link">{intl.formatMessage(messages['account.settings.delete.account.modal.after.button'])}</ModalCloseButton>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,7 +31,6 @@ export const SuccessModal = (props) => {
|
|||||||
|
|
||||||
SuccessModal.propTypes = {
|
SuccessModal.propTypes = {
|
||||||
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
status: PropTypes.oneOf(['confirming', 'pending', 'deleted', 'failed']),
|
||||||
intl: intlShape.isRequired,
|
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,4 +38,4 @@ SuccessModal.defaultProps = {
|
|||||||
status: null,
|
status: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(SuccessModal);
|
export default SuccessModal;
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import renderer from 'react-test-renderer';
|
import renderer from 'react-test-renderer';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
import { SuccessModal } from './SuccessModal';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
||||||
ReactDOM.createPortal = node => node;
|
jest.mock('react-dom', () => ({
|
||||||
|
...jest.requireActual('react-dom'),
|
||||||
import { SuccessModal } from './SuccessModal'; // eslint-disable-line import/first
|
createPortal: jest.fn(node => node), // Mock portal behavior
|
||||||
|
}));
|
||||||
const IntlSuccessModal = injectIntl(SuccessModal);
|
|
||||||
|
|
||||||
describe('SuccessModal', () => {
|
describe('SuccessModal', () => {
|
||||||
let props = {};
|
let props = {};
|
||||||
@@ -20,39 +19,40 @@ describe('SuccessModal', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match default closed success modal snapshot', () => {
|
it('should match default closed success modal snapshot', async () => {
|
||||||
let tree = renderer.create((
|
await waitFor(() => {
|
||||||
<IntlProvider locale="en"><IntlSuccessModal {...props} /></IntlProvider>))
|
const tree = renderer.create((
|
||||||
.toJSON();
|
<IntlProvider locale="en"><SuccessModal {...props} /></IntlProvider>)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
tree = renderer.create((
|
await waitFor(() => {
|
||||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="confirming" /></IntlProvider>))
|
const tree = renderer.create((
|
||||||
.toJSON();
|
<IntlProvider locale="en"><SuccessModal {...props} status="confirming" /></IntlProvider>)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
tree = renderer.create((
|
await waitFor(() => {
|
||||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="pending" /></IntlProvider>))
|
const tree = renderer.create((
|
||||||
.toJSON();
|
<IntlProvider locale="en"><SuccessModal {...props} status="pending" /></IntlProvider>)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
tree = renderer.create((
|
await waitFor(() => {
|
||||||
<IntlProvider locale="en"><IntlSuccessModal {...props} status="failed" /></IntlProvider>))
|
const tree = renderer.create((
|
||||||
.toJSON();
|
<IntlProvider locale="en"><SuccessModal {...props} status="failed" /></IntlProvider>)).toJSON();
|
||||||
expect(tree).toMatchSnapshot();
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should match open success modal snapshot', () => {
|
it('should match open success modal snapshot', async () => {
|
||||||
const tree = renderer
|
await waitFor(() => {
|
||||||
.create((
|
const tree = renderer.create(
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<IntlSuccessModal
|
<SuccessModal
|
||||||
{...props}
|
{...props}
|
||||||
status="deleted" // This will cause 'modal-backdrop' and 'show' to appear on the modal as CSS classes.
|
status="deleted"
|
||||||
/>
|
/>
|
||||||
</IntlProvider>
|
</IntlProvider>,
|
||||||
))
|
).toJSON();
|
||||||
.toJSON();
|
expect(tree).toMatchSnapshot();
|
||||||
expect(tree).toMatchSnapshot();
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`BeforeProceedingBanner should match the snapshot if SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT does not have a support link 1`] = `
|
||||||
|
<div
|
||||||
|
className="alert d-flex align-items-start alert-warning mt-n2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
|
data-icon="triangle-exclamation"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={{}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Before proceeding, please unlink all social media accounts.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`BeforeProceedingBanner should match the snapshot when SUPPORT_URL_TO_UNLINK_SOCIAL_MEDIA_ACCOUNT has a support link 1`] = `
|
||||||
|
<div
|
||||||
|
className="alert d-flex align-items-start alert-warning mt-n2"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
|
data-icon="triangle-exclamation"
|
||||||
|
data-prefix="fas"
|
||||||
|
focusable="false"
|
||||||
|
role="img"
|
||||||
|
style={{}}
|
||||||
|
viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
|
fill="currentColor"
|
||||||
|
style={{}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Before proceeding, please
|
||||||
|
<a
|
||||||
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
|
href="http://test-support.edx"
|
||||||
|
target="_self"
|
||||||
|
>
|
||||||
|
unlink all social media accounts
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -3,11 +3,11 @@
|
|||||||
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `null`;
|
exports[`ConfirmationModal should match default closed confirmation modal snapshot 1`] = `null`;
|
||||||
|
|
||||||
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
exports[`ConfirmationModal should match empty password confirmation modal snapshot 1`] = `
|
||||||
Array [
|
[
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -35,11 +35,14 @@ Array [
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
|
data-testid="modal-backdrop"
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
|
onKeyDown={[MockFunction]}
|
||||||
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Are you sure?"
|
aria-label="Are you sure?"
|
||||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
className="pgn__modal pgn__modal-md pgn__modal-default pgn__modal-visible-overflow pgn__alert-modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -67,19 +70,19 @@ Array [
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-exclamation-circle fa-w-16 mr-2"
|
className="svg-inline--fa fa-circle-exclamation mr-2"
|
||||||
data-icon="exclamation-circle"
|
data-icon="circle-exclamation"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
viewBox="0 0 512 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="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"
|
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24l0 112c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-112c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,19 +103,19 @@ Array [
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
data-icon="exclamation-triangle"
|
data-icon="triangle-exclamation"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
viewBox="0 0 576 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,29 +132,57 @@ Array [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="form-group"
|
className="pgn__form-group"
|
||||||
|
for="passwordFieldId"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="d-block"
|
className="pgn__form-label d-block"
|
||||||
htmlFor="passwordFieldId"
|
htmlFor="form-field3"
|
||||||
>
|
>
|
||||||
If you still wish to continue and delete your account, please enter your account password:
|
If you still wish to continue and delete your account, please enter your account password:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div
|
||||||
aria-describedby="passwordFieldId-invalid-feedback"
|
className="pgn__form-control-decorator-group"
|
||||||
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
|
<input
|
||||||
</strong>
|
aria-describedby="form-field3-5"
|
||||||
|
className="has-value form-control is-invalid"
|
||||||
|
id="form-field3"
|
||||||
|
name="password"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="password"
|
||||||
|
value="fluffy bunnies"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
|
||||||
|
feedback-for="passwordFieldId"
|
||||||
|
id="form-field3-5"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden={true}
|
||||||
|
fill="none"
|
||||||
|
focusable={false}
|
||||||
|
height={24}
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
A password is required
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,7 +218,7 @@ Array [
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -203,11 +234,11 @@ Array [
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
exports[`ConfirmationModal should match open confirmation modal snapshot 1`] = `
|
||||||
Array [
|
[
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -217,15 +248,17 @@ Array [
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
/>,
|
/>,
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-layer"
|
className="pgn__modal-layer"
|
||||||
data-focus-lock-disabled="disabled"
|
data-focus-lock-disabled={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
|
onMouseDown={[Function]}
|
||||||
onScrollCapture={[Function]}
|
onScrollCapture={[Function]}
|
||||||
onTouchMoveCapture={[Function]}
|
onTouchMoveCapture={[Function]}
|
||||||
|
onTouchStart={[Function]}
|
||||||
onWheelCapture={[Function]}
|
onWheelCapture={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -233,11 +266,14 @@ Array [
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
|
data-testid="modal-backdrop"
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
|
onKeyDown={[MockFunction]}
|
||||||
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-label="Are you sure?"
|
aria-label="Are you sure?"
|
||||||
className="pgn__modal pgn__modal-md pgn__modal-default pgn__alert-modal"
|
className="pgn__modal pgn__modal-md pgn__modal-default pgn__modal-visible-overflow pgn__alert-modal"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -265,19 +301,19 @@ Array [
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
data-icon="exclamation-triangle"
|
data-icon="triangle-exclamation"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
viewBox="0 0 576 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,29 +330,28 @@ Array [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="form-group"
|
className="pgn__form-group"
|
||||||
|
for="passwordFieldId"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="d-block"
|
className="pgn__form-label d-block"
|
||||||
htmlFor="passwordFieldId"
|
htmlFor="form-field1"
|
||||||
>
|
>
|
||||||
If you still wish to continue and delete your account, please enter your account password:
|
If you still wish to continue and delete your account, please enter your account password:
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div
|
||||||
aria-describedby=""
|
className="pgn__form-control-decorator-group"
|
||||||
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
|
<input
|
||||||
</strong>
|
className="has-value form-control"
|
||||||
|
id="form-field1"
|
||||||
|
name="password"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onChange={[Function]}
|
||||||
|
type="password"
|
||||||
|
value="fluffy bunnies"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,7 +387,7 @@ Array [
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -362,7 +397,7 @@ Array [
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ exports[`DeleteAccount should match default section snapshot 1`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -74,8 +73,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -97,19 +95,19 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
data-icon="exclamation-triangle"
|
data-icon="triangle-exclamation"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
viewBox="0 0 576 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,8 +115,7 @@ exports[`DeleteAccount should match unverified account section snapshot 1`] = `
|
|||||||
Before proceeding, please
|
Before proceeding, please
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email-"
|
href="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-confirm-my-email"
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
activate your account
|
activate your account
|
||||||
@@ -156,8 +153,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
<p>
|
<p>
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/sections/115004139268-Manage-Your-Account-Settings"
|
href="https://help.edx.org/edxlearner/s/topic/0TOQq0000001UdZOAU/account-basics"
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
Want to change your email, name, or password instead?
|
Want to change your email, name, or password instead?
|
||||||
@@ -179,19 +175,19 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
<div>
|
<div>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="svg-inline--fa fa-exclamation-triangle fa-w-18 mr-2"
|
className="svg-inline--fa fa-triangle-exclamation mr-2"
|
||||||
data-icon="exclamation-triangle"
|
data-icon="triangle-exclamation"
|
||||||
data-prefix="fas"
|
data-prefix="fas"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
role="img"
|
role="img"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
viewBox="0 0 576 512"
|
viewBox="0 0 512 512"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<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"
|
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
style={Object {}}
|
style={{}}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,8 +195,7 @@ exports[`DeleteAccount should match unverified account section snapshot 2`] = `
|
|||||||
Before proceeding, please
|
Before proceeding, please
|
||||||
<a
|
<a
|
||||||
className="pgn__hyperlink default-link standalone-link"
|
className="pgn__hyperlink default-link standalone-link"
|
||||||
href="https://support.edx.org/hc/en-us/articles/207206067"
|
href="https://help.edx.org/edxlearner/s/article/How-do-I-link-or-unlink-my-edX-account-to-a-social-media-account"
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
target="_self"
|
||||||
>
|
>
|
||||||
unlink all social media accounts
|
unlink all social media accounts
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ exports[`SuccessModal should match default closed success modal snapshot 3`] = `
|
|||||||
exports[`SuccessModal should match default closed success modal snapshot 4`] = `null`;
|
exports[`SuccessModal should match default closed success modal snapshot 4`] = `null`;
|
||||||
|
|
||||||
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
exports[`SuccessModal should match open success modal snapshot 1`] = `
|
||||||
Array [
|
[
|
||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -23,15 +23,17 @@ Array [
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
/>,
|
/>,
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-layer"
|
className="pgn__modal-layer"
|
||||||
data-focus-lock-disabled="disabled"
|
data-focus-lock-disabled={false}
|
||||||
onBlur={[Function]}
|
onBlur={[Function]}
|
||||||
onFocus={[Function]}
|
onFocus={[Function]}
|
||||||
|
onMouseDown={[Function]}
|
||||||
onScrollCapture={[Function]}
|
onScrollCapture={[Function]}
|
||||||
onTouchMoveCapture={[Function]}
|
onTouchMoveCapture={[Function]}
|
||||||
|
onTouchStart={[Function]}
|
||||||
onWheelCapture={[Function]}
|
onWheelCapture={[Function]}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -39,7 +41,10 @@ Array [
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="pgn__modal-backdrop"
|
className="pgn__modal-backdrop"
|
||||||
|
data-testid="modal-backdrop"
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
|
onKeyDown={[MockFunction]}
|
||||||
|
role="presentation"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="mw-sm p-5 bg-white mx-auto my-3"
|
className="mw-sm p-5 bg-white mx-auto my-3"
|
||||||
@@ -72,7 +77,7 @@ Array [
|
|||||||
<div
|
<div
|
||||||
data-focus-guard={true}
|
data-focus-guard={true}
|
||||||
style={
|
style={
|
||||||
Object {
|
{
|
||||||
"height": "0px",
|
"height": "0px",
|
||||||
"left": "1px",
|
"left": "1px",
|
||||||
"overflow": "hidden",
|
"overflow": "hidden",
|
||||||
@@ -82,7 +87,7 @@ Array [
|
|||||||
"width": "1px",
|
"width": "1px",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabIndex={-1}
|
tabIndex={0}
|
||||||
/>,
|
/>,
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|||||||
65
src/account-settings/delete-account/data/service.test.js
Normal file
65
src/account-settings/delete-account/data/service.test.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import formurlencoded from 'form-urlencoded';
|
||||||
|
import { handleRequestError } from '../../data/utils';
|
||||||
|
|
||||||
|
import { postDeleteAccount } from './service';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('form-urlencoded');
|
||||||
|
jest.mock('../../data/utils');
|
||||||
|
|
||||||
|
describe('postDeleteAccount', () => {
|
||||||
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({
|
||||||
|
LMS_BASE_URL: 'http://testserver',
|
||||||
|
});
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({
|
||||||
|
post: mockPost,
|
||||||
|
});
|
||||||
|
|
||||||
|
formurlencoded.mockImplementation(obj => `encoded:${JSON.stringify(obj)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts delete account request with password', async () => {
|
||||||
|
const mockResponse = { data: { success: true } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await postDeleteAccount('mypassword');
|
||||||
|
|
||||||
|
expect(getConfig).toHaveBeenCalled();
|
||||||
|
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
||||||
|
expect(formurlencoded).toHaveBeenCalledWith({ password: 'mypassword' });
|
||||||
|
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/api/user/v1/accounts/deactivate_logout/',
|
||||||
|
'encoded:{"password":"mypassword"}',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleRequestError and throws when request fails', async () => {
|
||||||
|
const mockError = new Error('Request failed');
|
||||||
|
mockPost.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
handleRequestError.mockImplementation(() => {
|
||||||
|
throw mockError;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(postDeleteAccount('wrongpassword')).rejects.toThrow('Request failed');
|
||||||
|
|
||||||
|
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
'account.settings.cannot.delete.account.text': {
|
||||||
|
id: 'account.settings.cannot.delete.account.text',
|
||||||
|
defaultMessage: 'Please note that, for legal and regulatory compliance purposes, account deletion is currently unavailable.',
|
||||||
|
description: 'This text is visible when user is not allowed to delete account',
|
||||||
|
},
|
||||||
'account.settings.delete.account.header': {
|
'account.settings.delete.account.header': {
|
||||||
id: 'account.settings.delete.account.header',
|
id: 'account.settings.delete.account.header',
|
||||||
defaultMessage: 'Delete My Account',
|
defaultMessage: 'Delete My Account',
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Form } from '@edx/paragon';
|
|
||||||
import { DECLINED } from '../data/constants';
|
|
||||||
|
|
||||||
const Checkboxes = (props) => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
options,
|
|
||||||
values,
|
|
||||||
onChange,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [selected, setSelected] = useState(values);
|
|
||||||
useEffect(() => {
|
|
||||||
onChange(id, selected);
|
|
||||||
}, [id, onChange, selected]);
|
|
||||||
|
|
||||||
const handleToggle = (value, option) => {
|
|
||||||
// If the user checked 'declined', uncheck all other options
|
|
||||||
if (value && option === DECLINED) {
|
|
||||||
setSelected([DECLINED]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If option checked, make sure this option is in `selected` (and remove 'declined')
|
|
||||||
if (value && !selected.includes(option)) {
|
|
||||||
const newSelected = selected.filter(i => i !== DECLINED).concat(option);
|
|
||||||
setSelected(newSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If unchecked, make sure this option is NOT in `selected`
|
|
||||||
if (!value) {
|
|
||||||
setSelected(selected.filter(i => i !== option));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCheckboxes = () => options.map((option, index) => {
|
|
||||||
const isFirst = index === 0;
|
|
||||||
const isChecked = selected.includes(option.value);
|
|
||||||
return (
|
|
||||||
<div key={option.value} className="checkboxOption">
|
|
||||||
<Form.Checkbox
|
|
||||||
type="checkbox"
|
|
||||||
id={option.value}
|
|
||||||
name={option.value}
|
|
||||||
value={option.value}
|
|
||||||
checked={isChecked}
|
|
||||||
autoFocus={isFirst}
|
|
||||||
onChange={(event) => handleToggle(event.target.checked, option.value)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Form.Checkbox>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div role="group">
|
|
||||||
{renderCheckboxes()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Checkboxes.propTypes = {
|
|
||||||
id: PropTypes.string.isRequired,
|
|
||||||
options: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
value: PropTypes.string,
|
|
||||||
label: PropTypes.string,
|
|
||||||
})),
|
|
||||||
values: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
Checkboxes.defaultProps = {
|
|
||||||
options: [],
|
|
||||||
values: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Checkboxes;
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
import { getConfig } from '@edx/frontend-platform';
|
|
||||||
import {
|
|
||||||
FormattedMessage,
|
|
||||||
injectIntl,
|
|
||||||
intlShape,
|
|
||||||
} from '@edx/frontend-platform/i18n';
|
|
||||||
|
|
||||||
import { Hyperlink, Form } from '@edx/paragon';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import get from 'lodash.get';
|
|
||||||
import isEmpty from 'lodash.isempty';
|
|
||||||
import memoize from 'memoize-one';
|
|
||||||
import { demographicsSectionSelector } from '../data/selectors';
|
|
||||||
import EditableSelectField from '../EditableSelectField';
|
|
||||||
import Checkboxes from './Checkboxes';
|
|
||||||
import Alert from '../Alert';
|
|
||||||
import { saveMultipleSettings, updateDraft } from '../data/actions';
|
|
||||||
import {
|
|
||||||
OTHER,
|
|
||||||
SELF_DESCRIBE,
|
|
||||||
} from '../data/constants';
|
|
||||||
import messages from './DemographicsSection.messages';
|
|
||||||
|
|
||||||
class DemographicsSection extends React.Component {
|
|
||||||
// We check the `demographicsOptions` prop to see if it is empty before we attempt to extract and
|
|
||||||
// format the available options for each question from the API response.
|
|
||||||
getApiOptions = memoize((demographicsOptions) => (this.hasRetrievedDemographicsOptions() && {
|
|
||||||
demographicsGenderOptions: this.addDefaultOption('account.settings.field.demographics.gender.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.gender.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
/* Ethnicity options don't need the blank/default option */
|
|
||||||
demographicsEthnicityOptions: demographicsOptions.actions.POST.user_ethnicity.child.children.ethnicity.choices.map(
|
|
||||||
key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
demographicsIncomeOptions: this.addDefaultOption('account.settings.field.demographics.income.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.income.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
demographicsMilitaryHistoryOptions: this.addDefaultOption('account.settings.field.demographics.military_history.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.military_history.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
demographicsEducationLevelOptions: this.addDefaultOption('account.settings.field.demographics.education_level.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.learner_education_level.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
demographicsWorkStatusOptions: this.addDefaultOption('account.settings.field.demographics.work_status.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.work_status.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
demographicsWorkSectorOptions: this.addDefaultOption('account.settings.field.demographics.work_sector.options.empty')
|
|
||||||
.concat(demographicsOptions.actions.POST.current_work_sector.choices.map(key => ({
|
|
||||||
value: key.value,
|
|
||||||
label: key.display_name,
|
|
||||||
}))),
|
|
||||||
}));
|
|
||||||
|
|
||||||
ethnicityFieldDisplay = (demographicsEthnicityOptions) => {
|
|
||||||
let ethnicities = [];
|
|
||||||
if (get(this, 'props.formValues.demographics_user_ethnicity')) {
|
|
||||||
ethnicities = this.props.formValues.demographics_user_ethnicity;
|
|
||||||
}
|
|
||||||
return ethnicities.map((e) => {
|
|
||||||
const matchingOption = demographicsEthnicityOptions.filter(option => option.value === e)[0];
|
|
||||||
return matchingOption && matchingOption.label;
|
|
||||||
}).join(', ');
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEditableFieldChange = (name, value) => {
|
|
||||||
this.props.updateDraft(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = (formId) => {
|
|
||||||
// We have some custom fields in this section. Instead of relying on the
|
|
||||||
// submitted values, submit the values stored in 'drafts'.
|
|
||||||
const { drafts } = this.props;
|
|
||||||
const settingsArray = Object.entries(drafts).map(([field, value]) => ({
|
|
||||||
formId: field,
|
|
||||||
commitValues: value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
this.props.saveMultipleSettings(settingsArray, formId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method that adds the specified message as a default option to the list of available
|
|
||||||
* choices.
|
|
||||||
*
|
|
||||||
* @param {*} messageId id of message matching desired default label text
|
|
||||||
*/
|
|
||||||
addDefaultOption(messageId) {
|
|
||||||
return [{
|
|
||||||
value: '',
|
|
||||||
label: this.props.intl.formatMessage(messages[messageId]),
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility method that helps determine if we were able to retrieve the available options for
|
|
||||||
* the Demographics questions. Returns true if the `demographicsOptions` prop is _not_ empty,
|
|
||||||
* otherwise false. This prop being empty is indicative of a failure communicating with the
|
|
||||||
* Demographics IDA's API.
|
|
||||||
*/
|
|
||||||
hasRetrievedDemographicsOptions() {
|
|
||||||
return !isEmpty(this.props.formValues.demographicsOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If an error is encountered when trying to communicate with the Demographics IDA then we will
|
|
||||||
* display an Alert letting the user know that their info will not be displayed and temporarily
|
|
||||||
* cannot be updated.
|
|
||||||
*/
|
|
||||||
renderDemographicsServiceIssueWarning() {
|
|
||||||
if (!isEmpty(this.props.formErrors.demographicsError)
|
|
||||||
|| !this.hasRetrievedDemographicsOptions()) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
tabIndex="-1"
|
|
||||||
ref={this.alertRef}
|
|
||||||
>
|
|
||||||
<Alert className="alert alert-danger" role="alert">
|
|
||||||
<FormattedMessage
|
|
||||||
id="account.settings.message.demographics.service.issue"
|
|
||||||
defaultMessage="An error occurred attempting to retrieve or save your account information. Please try again later."
|
|
||||||
description="alert message informing the user that the there is a problem retrieving or updating information from the Demographics microservice"
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const editableFieldProps = {
|
|
||||||
onChange: this.handleEditableFieldChange,
|
|
||||||
onSubmit: this.handleSubmit,
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
demographicsGenderOptions,
|
|
||||||
demographicsEthnicityOptions,
|
|
||||||
demographicsIncomeOptions,
|
|
||||||
demographicsMilitaryHistoryOptions,
|
|
||||||
demographicsEducationLevelOptions,
|
|
||||||
demographicsWorkStatusOptions,
|
|
||||||
demographicsWorkSectorOptions,
|
|
||||||
} = this.getApiOptions(this.props.formValues.demographicsOptions);
|
|
||||||
|
|
||||||
const showSelfDescribe = this.props.formValues.demographics_gender === SELF_DESCRIBE;
|
|
||||||
const showWorkStatusDescribe = this.props.formValues.demographics_work_status === OTHER;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="account-section pt-3 mb-5" id="demographics-information" ref={this.props.forwardRef}>
|
|
||||||
<h2 className="section-heading h4 mb-3">
|
|
||||||
{this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])}
|
|
||||||
</h2>
|
|
||||||
<p>
|
|
||||||
<Hyperlink
|
|
||||||
destination={`${getConfig().MARKETING_SITE_BASE_URL}/demographics`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{this.props.intl.formatMessage(
|
|
||||||
messages['account.settings.section.demographics.why'],
|
|
||||||
{
|
|
||||||
siteName: getConfig().SITE_NAME,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
</Hyperlink>
|
|
||||||
</p>
|
|
||||||
{this.renderDemographicsServiceIssueWarning()}
|
|
||||||
{/*
|
|
||||||
If the demographicsOptions props are empty then there is no need to display the fields as
|
|
||||||
the user will not have any choices available to select, nor will they be able to update
|
|
||||||
their answers.
|
|
||||||
*/}
|
|
||||||
{this.hasRetrievedDemographicsOptions() && (
|
|
||||||
<div id="demographics-fields">
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_gender"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_gender}
|
|
||||||
userSuppliedValue={showSelfDescribe ? this.props.formValues.demographics_gender_description : null}
|
|
||||||
options={demographicsGenderOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
>
|
|
||||||
{showSelfDescribe && (
|
|
||||||
<Form.Control
|
|
||||||
name="demographics_gender_description"
|
|
||||||
id="field-demographics_gender_description"
|
|
||||||
type="text"
|
|
||||||
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description.empty'])}
|
|
||||||
value={this.props.formValues.demographics_gender_description}
|
|
||||||
onChange={(e) => this.handleEditableFieldChange('demographics_gender_description', e.target.value)}
|
|
||||||
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description'])}
|
|
||||||
className="mt-1"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</EditableSelectField>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_user_ethnicity"
|
|
||||||
type="select"
|
|
||||||
hidden
|
|
||||||
value={this.ethnicityFieldDisplay(demographicsEthnicityOptions)}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.ethnicity.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
>
|
|
||||||
<Checkboxes
|
|
||||||
id="demographics_user_ethnicity"
|
|
||||||
options={demographicsEthnicityOptions}
|
|
||||||
values={this.props.formValues.demographics_user_ethnicity}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
</EditableSelectField>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_income"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_income}
|
|
||||||
options={demographicsIncomeOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.income'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.income.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_military_history"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_military_history}
|
|
||||||
options={demographicsMilitaryHistoryOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.military_history.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_learner_education_level"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_learner_education_level}
|
|
||||||
options={demographicsEducationLevelOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.learner_education_level.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_parent_education_level"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_parent_education_level}
|
|
||||||
options={demographicsEducationLevelOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.parent_education_level.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_work_status"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_work_status}
|
|
||||||
userSuppliedValue={showWorkStatusDescribe
|
|
||||||
? this.props.formValues.demographics_work_status_description
|
|
||||||
: null}
|
|
||||||
options={demographicsWorkStatusOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
>
|
|
||||||
{showWorkStatusDescribe && (
|
|
||||||
<Form.Control
|
|
||||||
name="demographics_work_status_description"
|
|
||||||
id="field-demographics_work_status_description"
|
|
||||||
type="text"
|
|
||||||
placeholder={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description.empty'])}
|
|
||||||
value={this.props.formValues.demographics_work_status_description}
|
|
||||||
onChange={(e) => this.handleEditableFieldChange('demographics_work_status_description', e.target.value)}
|
|
||||||
aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description'])}
|
|
||||||
className="mt-1"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</EditableSelectField>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_current_work_sector"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_current_work_sector}
|
|
||||||
options={demographicsWorkSectorOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.current_work_sector.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
<EditableSelectField
|
|
||||||
name="demographics_future_work_sector"
|
|
||||||
type="select"
|
|
||||||
value={this.props.formValues.demographics_future_work_sector}
|
|
||||||
options={demographicsWorkSectorOptions}
|
|
||||||
label={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector'])}
|
|
||||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.demographics.future_work_sector.empty'])}
|
|
||||||
{...editableFieldProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DemographicsSection.propTypes = {
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
formValues: PropTypes.shape({
|
|
||||||
demographics_gender: PropTypes.string,
|
|
||||||
demographics_user_ethnicity: PropTypes.shape([]),
|
|
||||||
demographics_income: PropTypes.string,
|
|
||||||
demographics_military_history: PropTypes.string,
|
|
||||||
demographics_learner_education_level: PropTypes.string,
|
|
||||||
demographics_parent_education_level: PropTypes.string,
|
|
||||||
demographics_work_status: PropTypes.string,
|
|
||||||
demographics_current_work_sector: PropTypes.string,
|
|
||||||
demographics_future_work_sector: PropTypes.string,
|
|
||||||
demographics_work_status_description: PropTypes.string,
|
|
||||||
demographics_gender_description: PropTypes.string,
|
|
||||||
demographicsOptions: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
drafts: PropTypes.shape({
|
|
||||||
demographics_gender: PropTypes.string,
|
|
||||||
demographics_user_ethnicity: PropTypes.shape([]),
|
|
||||||
demographics_income: PropTypes.string,
|
|
||||||
demographics_military_history: PropTypes.string,
|
|
||||||
demographics_learner_education_level: PropTypes.string,
|
|
||||||
demographics_parent_education_level: PropTypes.string,
|
|
||||||
demographics_work_status: PropTypes.string,
|
|
||||||
demographics_current_work_sector: PropTypes.string,
|
|
||||||
demographics_future_work_sector: PropTypes.string,
|
|
||||||
demographics_work_status_description: PropTypes.string,
|
|
||||||
demographics_gender_description: PropTypes.string,
|
|
||||||
demographicsOptions: PropTypes.shape({}),
|
|
||||||
}).isRequired,
|
|
||||||
formErrors: PropTypes.shape({
|
|
||||||
demographicsError: PropTypes.string,
|
|
||||||
}).isRequired,
|
|
||||||
forwardRef: PropTypes.func.isRequired,
|
|
||||||
updateDraft: PropTypes.func.isRequired,
|
|
||||||
saveMultipleSettings: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(demographicsSectionSelector, {
|
|
||||||
saveMultipleSettings,
|
|
||||||
updateDraft,
|
|
||||||
})(injectIntl(DemographicsSection));
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
/* 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,3946 +0,0 @@
|
|||||||
// 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>
|
|
||||||
`;
|
|
||||||
19
src/account-settings/hoc.jsx
Normal file
19
src/account-settings/hoc.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const withNavigate = Component => {
|
||||||
|
const WrappedComponent = props => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
return <Component {...props} navigate={navigate} />;
|
||||||
|
};
|
||||||
|
return WrappedComponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const withLocation = Component => {
|
||||||
|
const WrappedComponent = props => {
|
||||||
|
const location = useLocation();
|
||||||
|
return <Component {...props} location={location.pathname} />;
|
||||||
|
};
|
||||||
|
return WrappedComponent;
|
||||||
|
};
|
||||||
38
src/account-settings/hoc.test.jsx
Normal file
38
src/account-settings/hoc.test.jsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
import { withLocation, withNavigate } from './hoc';
|
||||||
|
|
||||||
|
const mockedNavigator = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useNavigate: () => mockedNavigator,
|
||||||
|
useLocation: () => ({
|
||||||
|
pathname: '/current-location',
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const MockComponent = ({ navigate, location }) => (
|
||||||
|
// eslint-disable-next-line react/button-has-type, react/prop-types
|
||||||
|
<button data-testid="btn" onClick={() => navigate('/some-route')}>{location}</button>
|
||||||
|
);
|
||||||
|
const WrappedComponent = withNavigate(withLocation(MockComponent));
|
||||||
|
|
||||||
|
test('Provide Navigation to Component', () => {
|
||||||
|
render(
|
||||||
|
<WrappedComponent />,
|
||||||
|
);
|
||||||
|
const btn = screen.getByTestId('btn');
|
||||||
|
fireEvent.click(btn);
|
||||||
|
|
||||||
|
expect(mockedNavigator).toHaveBeenCalledWith('/some-route');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Provide Location Pathname to Component', () => {
|
||||||
|
render(
|
||||||
|
<WrappedComponent />,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('btn').textContent).toContain('/current-location');
|
||||||
|
});
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { connect, useDispatch } from 'react-redux';
|
import { connect, useDispatch } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import {
|
||||||
ActionRow,
|
ActionRow,
|
||||||
Alert,
|
Alert,
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
StatefulButton,
|
StatefulButton,
|
||||||
} from '@edx/paragon';
|
} from '@openedx/paragon';
|
||||||
|
|
||||||
import { closeForm, saveSettingsReset } from '../data/actions';
|
import { closeForm, saveSettingsReset } from '../data/actions';
|
||||||
import { nameChangeSelector } from '../data/selectors';
|
import { nameChangeSelector } from '../data/selectors';
|
||||||
@@ -25,14 +25,14 @@ const NameChangeModal = ({
|
|||||||
targetFormId,
|
targetFormId,
|
||||||
errors,
|
errors,
|
||||||
formValues,
|
formValues,
|
||||||
intl,
|
|
||||||
saveState,
|
saveState,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { push } = useHistory();
|
const navigate = useNavigate();
|
||||||
const { username } = getAuthenticatedUser();
|
const { username } = getAuthenticatedUser();
|
||||||
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
const [verifiedNameInput, setVerifiedNameInput] = useState(formValues.verified_name || '');
|
||||||
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
const [confirmedWarning, setConfirmedWarning] = useState(false);
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
const resetLocalState = useCallback(() => {
|
const resetLocalState = useCallback(() => {
|
||||||
setConfirmedWarning(false);
|
setConfirmedWarning(false);
|
||||||
@@ -69,9 +69,9 @@ const NameChangeModal = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (saveState === 'complete') {
|
if (saveState === 'complete') {
|
||||||
handleClose();
|
handleClose();
|
||||||
push(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
navigate(`/id-verification?next=${encodeURIComponent('account/settings')}`);
|
||||||
}
|
}
|
||||||
}, [handleClose, push, saveState]);
|
}, [handleClose, navigate, saveState]);
|
||||||
|
|
||||||
function renderErrors() {
|
function renderErrors() {
|
||||||
if (Object.keys(errors).length > 0) {
|
if (Object.keys(errors).length > 0) {
|
||||||
@@ -193,11 +193,10 @@ NameChangeModal.propTypes = {
|
|||||||
verified_name: PropTypes.string,
|
verified_name: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
saveState: PropTypes.string,
|
saveState: PropTypes.string,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
NameChangeModal.defaultProps = {
|
NameChangeModal.defaultProps = {
|
||||||
saveState: null,
|
saveState: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(nameChangeSelector)(injectIntl(NameChangeModal));
|
export default connect(nameChangeSelector)(NameChangeModal);
|
||||||
|
|||||||
56
src/account-settings/name-change/data/service.test.js
Normal file
56
src/account-settings/name-change/data/service.test.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { handleRequestError } from '../../data/utils';
|
||||||
|
|
||||||
|
import { postNameChange } from './service';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('../../data/utils');
|
||||||
|
|
||||||
|
describe('postNameChange', () => {
|
||||||
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({
|
||||||
|
LMS_BASE_URL: 'http://testserver',
|
||||||
|
});
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({
|
||||||
|
post: mockPost,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts a name change request successfully', async () => {
|
||||||
|
const mockResponse = { data: { success: true, updated: true } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await postNameChange('New Name');
|
||||||
|
|
||||||
|
expect(getConfig).toHaveBeenCalled();
|
||||||
|
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/api/user/v1/accounts/name_change/',
|
||||||
|
{ name: 'New Name' },
|
||||||
|
{ headers: { Accept: 'application/json' } },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleRequestError and throws when request fails', async () => {
|
||||||
|
const mockError = new Error('Request failed');
|
||||||
|
mockPost.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
handleRequestError.mockImplementation(() => {
|
||||||
|
throw mockError;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(postNameChange('Bad Name')).rejects.toThrow('Request failed');
|
||||||
|
|
||||||
|
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -23,7 +23,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
'account.settings.name.change.id.name.label': {
|
'account.settings.name.change.id.name.label': {
|
||||||
id: 'account.settings.name.change.id.name.label',
|
id: 'account.settings.name.change.id.name.label',
|
||||||
defaultMessage: 'Enter your name as it appears on your unexpired student, work, or government-issued identification card.',
|
defaultMessage: 'Enter your name as it appears on your identification card.',
|
||||||
description: 'Form label instructing the user to enter the name on their ID.',
|
description: 'Form label instructing the user to enter the name on their ID.',
|
||||||
},
|
},
|
||||||
'account.settings.name.change.id.name.placeholder': {
|
'account.settings.name.change.id.name.placeholder': {
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { Router } from 'react-router-dom';
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
import configureStore from 'redux-mock-store';
|
import configureStore from 'redux-mock-store';
|
||||||
import {
|
import {
|
||||||
fireEvent,
|
fireEvent,
|
||||||
render,
|
render,
|
||||||
screen,
|
screen,
|
||||||
} from '@testing-library/react';
|
} from '@testing-library/react';
|
||||||
import { createMemoryHistory } from 'history';
|
|
||||||
|
|
||||||
import * as auth from '@edx/frontend-platform/auth';
|
import * as auth from '@edx/frontend-platform/auth';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest.
|
// Modal creates a portal. Overriding createPortal allows portals to be tested in jest.
|
||||||
ReactDOM.createPortal = node => node;
|
jest.mock('react-dom', () => ({
|
||||||
|
...jest.requireActual('react-dom'),
|
||||||
|
createPortal: jest.fn(node => node), // Mock portal behavior
|
||||||
|
}));
|
||||||
|
|
||||||
import NameChange from '../NameChange'; // eslint-disable-line import/first
|
import NameChange from '../NameChange'; // eslint-disable-line import/first
|
||||||
|
|
||||||
@@ -28,10 +28,6 @@ jest.mock('react-redux', () => ({
|
|||||||
jest.mock('@edx/frontend-platform/auth');
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ nameChangeSelector: () => ({}) })));
|
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ nameChangeSelector: () => ({}) })));
|
||||||
|
|
||||||
const history = createMemoryHistory();
|
|
||||||
|
|
||||||
const IntlNameChange = injectIntl(NameChange);
|
|
||||||
|
|
||||||
const mockStore = configureStore();
|
const mockStore = configureStore();
|
||||||
|
|
||||||
describe('NameChange', () => {
|
describe('NameChange', () => {
|
||||||
@@ -39,7 +35,7 @@ describe('NameChange', () => {
|
|||||||
let store = {};
|
let store = {};
|
||||||
|
|
||||||
const reduxWrapper = children => (
|
const reduxWrapper = children => (
|
||||||
<Router history={history}>
|
<Router>
|
||||||
<IntlProvider locale="en">
|
<IntlProvider locale="en">
|
||||||
<Provider store={store}>{children}</Provider>
|
<Provider store={store}>{children}</Provider>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
@@ -56,7 +52,6 @@ describe('NameChange', () => {
|
|||||||
verified_name: 'edX Verified',
|
verified_name: 'edX Verified',
|
||||||
},
|
},
|
||||||
saveState: null,
|
saveState: null,
|
||||||
intl: {},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
auth.getAuthenticatedHttpClient = jest.fn(() => ({
|
||||||
@@ -73,7 +68,7 @@ describe('NameChange', () => {
|
|||||||
it('renders populated input after clicking continue if verified_name in form data', async () => {
|
it('renders populated input after clicking continue if verified_name in form data', async () => {
|
||||||
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
const getInput = () => screen.queryByPlaceholderText('Enter the name on your photo ID');
|
||||||
|
|
||||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
render(reduxWrapper(<NameChange {...props} />));
|
||||||
expect(getInput()).toBeNull();
|
expect(getInput()).toBeNull();
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
@@ -90,7 +85,7 @@ describe('NameChange', () => {
|
|||||||
name: 'edx edx',
|
name: 'edx edx',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
render(reduxWrapper(<NameChange {...formProps} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -108,7 +103,7 @@ describe('NameChange', () => {
|
|||||||
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
type: 'ACCOUNT_SETTINGS__REQUEST_NAME_CHANGE',
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
render(reduxWrapper(<NameChange {...props} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -135,7 +130,7 @@ describe('NameChange', () => {
|
|||||||
targetFormId: 'name',
|
targetFormId: 'name',
|
||||||
};
|
};
|
||||||
|
|
||||||
render(reduxWrapper(<IntlNameChange {...formProps} />));
|
render(reduxWrapper(<NameChange {...formProps} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -151,7 +146,7 @@ describe('NameChange', () => {
|
|||||||
it('does not dispatch action while pending', async () => {
|
it('does not dispatch action while pending', async () => {
|
||||||
props.saveState = 'pending';
|
props.saveState = 'pending';
|
||||||
|
|
||||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
render(reduxWrapper(<NameChange {...props} />));
|
||||||
|
|
||||||
const continueButton = screen.getByText('Continue');
|
const continueButton = screen.getByText('Continue');
|
||||||
fireEvent.click(continueButton);
|
fireEvent.click(continueButton);
|
||||||
@@ -167,7 +162,7 @@ describe('NameChange', () => {
|
|||||||
it('routes to IDV when name change request is successful', async () => {
|
it('routes to IDV when name change request is successful', async () => {
|
||||||
props.saveState = 'complete';
|
props.saveState = 'complete';
|
||||||
|
|
||||||
render(reduxWrapper(<IntlNameChange {...props} />));
|
render(reduxWrapper(<NameChange {...props} />));
|
||||||
expect(history.location.pathname).toEqual('/id-verification');
|
expect(window.location.pathname).toEqual('/id-verification');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import { Hyperlink } from '@edx/paragon';
|
import { Hyperlink } from '@openedx/paragon';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ const ConfirmationAlert = (props) => {
|
|||||||
|
|
||||||
const technicalSupportLink = (
|
const technicalSupportLink = (
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
destination="https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-"
|
destination={getConfig().PASSWORD_RESET_SUPPORT_LINK}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
|
id="account.settings.editable.field.password.reset.button.confirmation.support.link"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
import { StatefulButton } from '@edx/paragon';
|
import { StatefulButton } from '@openedx/paragon';
|
||||||
|
|
||||||
import { resetPassword } from './data/actions';
|
import { resetPassword } from './data/actions';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -10,7 +9,9 @@ import ConfirmationAlert from './ConfirmationAlert';
|
|||||||
import RequestInProgressAlert from './RequestInProgressAlert';
|
import RequestInProgressAlert from './RequestInProgressAlert';
|
||||||
|
|
||||||
const ResetPassword = (props) => {
|
const ResetPassword = (props) => {
|
||||||
const { email, intl, status } = props;
|
const { email, status } = props;
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<h6 aria-level="3">
|
<h6 aria-level="3">
|
||||||
@@ -51,7 +52,6 @@ const ResetPassword = (props) => {
|
|||||||
|
|
||||||
ResetPassword.propTypes = {
|
ResetPassword.propTypes = {
|
||||||
email: PropTypes.string,
|
email: PropTypes.string,
|
||||||
intl: intlShape.isRequired,
|
|
||||||
resetPassword: PropTypes.func.isRequired,
|
resetPassword: PropTypes.func.isRequired,
|
||||||
status: PropTypes.string,
|
status: PropTypes.string,
|
||||||
};
|
};
|
||||||
@@ -68,4 +68,4 @@ export default connect(
|
|||||||
{
|
{
|
||||||
resetPassword,
|
resetPassword,
|
||||||
},
|
},
|
||||||
)(injectIntl(ResetPassword));
|
)(ResetPassword);
|
||||||
|
|||||||
65
src/account-settings/reset-password/data/service.test.js
Normal file
65
src/account-settings/reset-password/data/service.test.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import formurlencoded from 'form-urlencoded';
|
||||||
|
import { handleRequestError } from '../../data/utils';
|
||||||
|
|
||||||
|
import { postResetPassword } from './service';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('form-urlencoded');
|
||||||
|
jest.mock('../../data/utils');
|
||||||
|
|
||||||
|
describe('postResetPassword', () => {
|
||||||
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({
|
||||||
|
LMS_BASE_URL: 'http://testserver',
|
||||||
|
});
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({
|
||||||
|
post: mockPost,
|
||||||
|
});
|
||||||
|
|
||||||
|
formurlencoded.mockImplementation(obj => `encoded:${JSON.stringify(obj)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('posts reset password request with email', async () => {
|
||||||
|
const mockResponse = { data: { success: true, email_sent: true } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await postResetPassword('user@example.com');
|
||||||
|
|
||||||
|
expect(getConfig).toHaveBeenCalled();
|
||||||
|
expect(getAuthenticatedHttpClient).toHaveBeenCalled();
|
||||||
|
expect(formurlencoded).toHaveBeenCalledWith({ email: 'user@example.com' });
|
||||||
|
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/password_reset/',
|
||||||
|
'encoded:{"email":"user@example.com"}',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls handleRequestError and throws when request fails', async () => {
|
||||||
|
const mockError = new Error('Reset password failed');
|
||||||
|
mockPost.mockRejectedValueOnce(mockError);
|
||||||
|
|
||||||
|
handleRequestError.mockImplementation(() => {
|
||||||
|
throw mockError;
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(postResetPassword('bad@example.com')).rejects.toThrow('Reset password failed');
|
||||||
|
|
||||||
|
expect(handleRequestError).toHaveBeenCalledWith(mockError);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,8 +2,9 @@ import { AsyncActionType } from '../data/utils';
|
|||||||
|
|
||||||
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
||||||
|
|
||||||
export const fetchSiteLanguages = () => ({
|
export const fetchSiteLanguages = handleNavigation => ({
|
||||||
type: FETCH_SITE_LANGUAGES.BASE,
|
type: FETCH_SITE_LANGUAGES.BASE,
|
||||||
|
payload: { handleNavigation },
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchSiteLanguagesBegin = () => ({
|
export const fetchSiteLanguagesBegin = () => ({
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ const siteLanguageList = [
|
|||||||
name: 'Español (Latinoamérica)',
|
name: 'Español (Latinoamérica)',
|
||||||
released: true,
|
released: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: 'fa-ir',
|
||||||
|
name: 'فارسی',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: 'fr',
|
code: 'fr',
|
||||||
name: 'Français',
|
name: 'Français',
|
||||||
@@ -69,6 +74,61 @@ const siteLanguageList = [
|
|||||||
name: '中文 (简体)',
|
name: '中文 (简体)',
|
||||||
released: true,
|
released: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: 'pt-pt',
|
||||||
|
name: 'Português',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'it-it',
|
||||||
|
name: 'Italian',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'de-de',
|
||||||
|
name: 'German',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'hi',
|
||||||
|
name: 'Hindi',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'fr-ca',
|
||||||
|
name: 'French (CA)',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'te',
|
||||||
|
name: 'తెలుగు',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'da',
|
||||||
|
name: 'dansk',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'el',
|
||||||
|
name: 'Ελληνικά',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'es-es',
|
||||||
|
name: 'Español (España)',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'sw',
|
||||||
|
name: 'Kiswahili',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'tr-tr',
|
||||||
|
name: 'Türkçe (Türkiye)',
|
||||||
|
released: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default siteLanguageList;
|
export default siteLanguageList;
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import {
|
|||||||
import { getSiteLanguageList } from './service';
|
import { getSiteLanguageList } from './service';
|
||||||
import { handleFailure } from '../data/utils';
|
import { handleFailure } from '../data/utils';
|
||||||
|
|
||||||
function* handleFetchSiteLanguages() {
|
function* handleFetchSiteLanguages(action) {
|
||||||
try {
|
try {
|
||||||
yield put(fetchSiteLanguagesBegin());
|
yield put(fetchSiteLanguagesBegin());
|
||||||
const siteLanguageList = yield call(getSiteLanguageList);
|
const siteLanguageList = yield call(getSiteLanguageList);
|
||||||
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield call(handleFailure, e, fetchSiteLanguagesFailure);
|
yield call(handleFailure, e, action.payload.handleNavigation, fetchSiteLanguagesFailure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,15 @@ export async function patchPreferences(username, params) {
|
|||||||
|
|
||||||
export async function postSetLang(code) {
|
export async function postSetLang(code) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
const requestConfig = {
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const url = `${getConfig().LMS_BASE_URL}/i18n/setlang/`;
|
||||||
formData.append('language', code);
|
formData.append('language', code);
|
||||||
|
|
||||||
await getAuthenticatedHttpClient()
|
await getAuthenticatedHttpClient()
|
||||||
.post(`${getConfig().LMS_BASE_URL}/i18n/setlang/`, formData, {
|
.post(url, formData, requestConfig);
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
95
src/account-settings/site-language/service.test.js
Normal file
95
src/account-settings/site-language/service.test.js
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
|
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||||
|
import { convertKeyNames, snakeCaseObject } from '@edx/frontend-platform/utils';
|
||||||
|
|
||||||
|
import { getSiteLanguageList, patchPreferences, postSetLang } from './service';
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform');
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('@edx/frontend-platform/utils');
|
||||||
|
jest.mock('./constants', () => (['en', 'es', 'fr']));
|
||||||
|
|
||||||
|
describe('preferencesApi', () => {
|
||||||
|
const mockPatch = jest.fn();
|
||||||
|
const mockPost = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
|
||||||
|
getConfig.mockReturnValue({
|
||||||
|
LMS_BASE_URL: 'http://testserver',
|
||||||
|
});
|
||||||
|
|
||||||
|
getAuthenticatedHttpClient.mockReturnValue({
|
||||||
|
patch: mockPatch,
|
||||||
|
post: mockPost,
|
||||||
|
});
|
||||||
|
|
||||||
|
snakeCaseObject.mockImplementation(obj => obj);
|
||||||
|
convertKeyNames.mockImplementation((obj) => obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSiteLanguageList', () => {
|
||||||
|
it('returns the siteLanguageList constant', async () => {
|
||||||
|
const result = await getSiteLanguageList();
|
||||||
|
expect(result).toEqual(['en', 'es', 'fr']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('patchPreferences', () => {
|
||||||
|
it('patches preferences with processed params and returns the original params', async () => {
|
||||||
|
const username = 'testuser';
|
||||||
|
const params = { prefLang: 'en', darkMode: true };
|
||||||
|
const processed = { 'pref-lang': 'en', dark_mode: true };
|
||||||
|
|
||||||
|
// Mock conversions
|
||||||
|
snakeCaseObject.mockReturnValueOnce({ pref_lang: 'en', dark_mode: true });
|
||||||
|
convertKeyNames.mockReturnValueOnce(processed);
|
||||||
|
|
||||||
|
mockPatch.mockResolvedValueOnce({ data: { success: true } });
|
||||||
|
|
||||||
|
const result = await patchPreferences(username, params);
|
||||||
|
|
||||||
|
expect(snakeCaseObject).toHaveBeenCalledWith(params);
|
||||||
|
expect(convertKeyNames).toHaveBeenCalledWith(
|
||||||
|
{ pref_lang: 'en', dark_mode: true },
|
||||||
|
{ pref_lang: 'pref-lang' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockPatch).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/api/user/v1/preferences/testuser',
|
||||||
|
processed,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/merge-patch+json' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toEqual(params);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('postSetLang', () => {
|
||||||
|
it('posts language selection via FormData', async () => {
|
||||||
|
const mockResponse = { data: { success: true } };
|
||||||
|
mockPost.mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const appendSpy = jest.spyOn(FormData.prototype, 'append');
|
||||||
|
|
||||||
|
await postSetLang('fr');
|
||||||
|
|
||||||
|
expect(appendSpy).toHaveBeenCalledWith('language', 'fr');
|
||||||
|
expect(mockPost).toHaveBeenCalledWith(
|
||||||
|
'http://testserver/i18n/setlang/',
|
||||||
|
expect.any(FormData),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
appendSpy.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
234
src/account-settings/test/AccountSettingsPage.test.jsx
Normal file
234
src/account-settings/test/AccountSettingsPage.test.jsx
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import {
|
||||||
|
render, screen, fireEvent,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import AccountSettingsPage from '../AccountSettingsPage';
|
||||||
|
import mockData from './mockData';
|
||||||
|
import messages from '../AccountSettingsPage.messages';
|
||||||
|
|
||||||
|
const mockDispatch = jest.fn();
|
||||||
|
jest.mock('@edx/frontend-platform/analytics', () => ({
|
||||||
|
sendTrackingLogEvent: jest.fn(),
|
||||||
|
getCountryList: jest.fn(() => [{ code: 'US', name: 'United States' }]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('react-redux', () => ({
|
||||||
|
...jest.requireActual('react-redux'),
|
||||||
|
useDispatch: () => mockDispatch,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform'),
|
||||||
|
getConfig: jest.fn(() => ({
|
||||||
|
SITE_NAME: 'edX',
|
||||||
|
SUPPORT_URL: 'https://support.edx.org',
|
||||||
|
ENABLE_ACCOUNT_DELETION: true,
|
||||||
|
ENABLE_COPPA_COMPLIANCE: false,
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
||||||
|
})),
|
||||||
|
getCountryList: jest.fn(() => [{ code: 'US', name: 'United States' }]),
|
||||||
|
getLanguageList: jest.fn(() => [{ code: 'en', name: 'English' }]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const IntlAccountSettingsPage = injectIntl(AccountSettingsPage);
|
||||||
|
|
||||||
|
const middlewares = [thunk];
|
||||||
|
const mockStore = configureStore(middlewares);
|
||||||
|
|
||||||
|
describe('AccountSettingsPage', () => {
|
||||||
|
let props = {};
|
||||||
|
let store = {};
|
||||||
|
const appContext = { locale: 'en', authenticatedUser: { userId: 3, roles: [] } };
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<AppContext.Provider value={appContext}>
|
||||||
|
<Router>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<Provider store={store}>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
</Router>
|
||||||
|
</AppContext.Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore(mockData);
|
||||||
|
props = {
|
||||||
|
loaded: true,
|
||||||
|
siteLanguage: {},
|
||||||
|
formValues: {
|
||||||
|
username: 'test_username',
|
||||||
|
accomplishments_shared: false,
|
||||||
|
name: 'test_name',
|
||||||
|
email: 'test_email@test.com',
|
||||||
|
id: 534,
|
||||||
|
extended_profile: [
|
||||||
|
{
|
||||||
|
field_name: 'work_experience',
|
||||||
|
field_value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
country: 'US',
|
||||||
|
level_of_education: 'b',
|
||||||
|
gender: 'm',
|
||||||
|
language_proficiencies: 'es',
|
||||||
|
social_link_linkedin: 'https://linkedin.com/in/testuser',
|
||||||
|
social_link_facebook: '',
|
||||||
|
social_link_twitter: '',
|
||||||
|
time_zone: 'America/New_York',
|
||||||
|
state: 'NY',
|
||||||
|
secondary_email_enabled: true,
|
||||||
|
secondary_email: 'test_recovery@test.com',
|
||||||
|
year_of_birth: '1990',
|
||||||
|
},
|
||||||
|
fetchSettings: jest.fn(),
|
||||||
|
fetchSiteLanguages: jest.fn(),
|
||||||
|
fetchNotificationPreferences: jest.fn(),
|
||||||
|
saveSettings: jest.fn(),
|
||||||
|
updateDraft: jest.fn(),
|
||||||
|
beginNameChange: jest.fn(),
|
||||||
|
saveMultipleSettings: jest.fn(),
|
||||||
|
timeZoneOptions: [
|
||||||
|
{ label: 'America/New_York', value: 'America/New_York' },
|
||||||
|
],
|
||||||
|
countryTimeZoneOptions: [
|
||||||
|
{ label: 'America/New_York', value: 'America/New_York' },
|
||||||
|
],
|
||||||
|
siteLanguageOptions: [
|
||||||
|
{ label: 'English', value: 'en' },
|
||||||
|
],
|
||||||
|
tpaProviders: [
|
||||||
|
{
|
||||||
|
id: 'oa2-google-oauth2',
|
||||||
|
name: 'Google',
|
||||||
|
connected: false,
|
||||||
|
accepts_logins: true,
|
||||||
|
connectUrl: 'http://localhost:18000/auth/login/google-oauth2/',
|
||||||
|
disconnectUrl: 'http://localhost:18000/auth/disconnect/google-oauth2/',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isActive: true,
|
||||||
|
staticFields: [],
|
||||||
|
profileDataManager: null,
|
||||||
|
verifiedName: null,
|
||||||
|
mostRecentVerifiedName: {},
|
||||||
|
verifiedNameHistory: [],
|
||||||
|
countriesCodesList: ['US'],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
global.lightningjs = {
|
||||||
|
require: jest.fn().mockImplementation((module, url) => ({ moduleName: module, url })),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
delete global.lightningjs;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders AccountSettingsPage correctly with editing enabled', async () => {
|
||||||
|
const { getByText, rerender, getByLabelText } = render(reduxWrapper(<IntlAccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
const workExperienceText = getByText('Work Experience');
|
||||||
|
const workExperienceEditButton = workExperienceText.parentElement.querySelector('button');
|
||||||
|
|
||||||
|
expect(workExperienceEditButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
store = mockStore({
|
||||||
|
...mockData,
|
||||||
|
accountSettings: {
|
||||||
|
...mockData.accountSettings,
|
||||||
|
openFormId: 'work_experience',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
rerender(reduxWrapper(<IntlAccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
const submitButton = screen.getByText('Save');
|
||||||
|
expect(submitButton).toBeInTheDocument();
|
||||||
|
|
||||||
|
const workExperienceSelect = getByLabelText('Work Experience');
|
||||||
|
|
||||||
|
// Use fireEvent.change to simulate changing the selected value
|
||||||
|
fireEvent.change(workExperienceSelect, { target: { value: '4' } });
|
||||||
|
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Account Information section with correct field values', () => {
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.getByText('test_username')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('test_name')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('test_email@test.com')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('test_recovery@test.com')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('1990')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Profile Information section with correct field values', () => {
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.getByText('Bachelor\'s Degree')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Male')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Add work experience')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('English')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Social Media section with correct field values', () => {
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.getByText('https://linkedin.com/in/testuser')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Add Facebook profile')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(messages['account.settings.field.social.platform.name.xTwitter.empty'].defaultMessage)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Site Preferences section with correct field values', () => {
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.getByText('English')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('America/New_York')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders Delete Account section when enabled', () => {
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
const { getConfig } = require('@edx/frontend-platform');
|
||||||
|
jest.spyOn({ getConfig }, 'getConfig').mockImplementation(() => ({
|
||||||
|
SITE_NAME: 'edX',
|
||||||
|
SUPPORT_URL: 'https://support.edx.org',
|
||||||
|
ENABLE_ACCOUNT_DELETION: true,
|
||||||
|
ENABLE_COPPA_COMPLIANCE: false,
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.getByText('We\'re sorry to see you go!')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render Delete Account section when disabled', () => {
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
const { getConfig } = require('@edx/frontend-platform');
|
||||||
|
jest.spyOn({ getConfig }, 'getConfig').mockImplementation(() => ({
|
||||||
|
SITE_NAME: 'edX',
|
||||||
|
SUPPORT_URL: 'https://support.edx.org',
|
||||||
|
ENABLE_ACCOUNT_DELETION: false,
|
||||||
|
ENABLE_COPPA_COMPLIANCE: false,
|
||||||
|
COUNTRIES_WITH_DELETE_ACCOUNT_DISABLED: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
render(reduxWrapper(<AccountSettingsPage {...props} />));
|
||||||
|
|
||||||
|
expect(screen.queryByText('We\'re sorry to see you go!')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
144
src/account-settings/test/DOBForm.test.jsx
Normal file
144
src/account-settings/test/DOBForm.test.jsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import {
|
||||||
|
render, screen, fireEvent, waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { act } from 'react-dom/test-utils';
|
||||||
|
import * as reactRedux from 'react-redux';
|
||||||
|
import DOBModal from '../DOBForm';
|
||||||
|
import messages from '../AccountSettingsPage.messages';
|
||||||
|
import { YEAR_OF_BIRTH_OPTIONS } from '../data/constants';
|
||||||
|
|
||||||
|
jest.mock('react-redux', () => ({
|
||||||
|
...jest.requireActual('react-redux'),
|
||||||
|
useDispatch: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/i18n', () => ({
|
||||||
|
...jest.requireActual('@edx/frontend-platform/i18n'),
|
||||||
|
useIntl: () => ({
|
||||||
|
formatMessage: (message) => message.defaultMessage,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@openedx/paragon', () => ({
|
||||||
|
...jest.requireActual('@openedx/paragon'),
|
||||||
|
Form: {
|
||||||
|
...jest.requireActual('@openedx/paragon').Form,
|
||||||
|
Control: {
|
||||||
|
...jest.requireActual('@openedx/paragon').Form.Control,
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
Feedback: ({ children, ...props }) => <div {...props}>{children}</div>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockStore = configureStore([]);
|
||||||
|
|
||||||
|
describe('DOBModal', () => {
|
||||||
|
let store;
|
||||||
|
let mockDispatch;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore({
|
||||||
|
accountSettings: {
|
||||||
|
saveState: 'default',
|
||||||
|
errors: {},
|
||||||
|
openFormId: null,
|
||||||
|
confirmationValues: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mockDispatch = jest.fn();
|
||||||
|
jest.spyOn(reactRedux, 'useDispatch').mockReturnValue(mockDispatch); // ✅ replaced require with import
|
||||||
|
// Mock localStorage.setItem
|
||||||
|
Object.defineProperty(window, 'localStorage', {
|
||||||
|
value: {
|
||||||
|
setItem: jest.fn(),
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderComponent = (props = {}) => render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<DOBModal
|
||||||
|
saveState="default"
|
||||||
|
error={undefined}
|
||||||
|
onSubmit={jest.fn()}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
it('renders the modal with correct elements', async () => {
|
||||||
|
renderComponent();
|
||||||
|
const openButton = screen.getByTestId('open-modal-button');
|
||||||
|
expect(openButton).toHaveTextContent(messages['account.settings.field.dob.form.button'].defaultMessage);
|
||||||
|
|
||||||
|
fireEvent.click(openButton);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('modal-title')).toHaveTextContent(messages['account.settings.field.dob.form.title'].defaultMessage);
|
||||||
|
expect(screen.getByTestId('help-text')).toHaveTextContent(messages['account.settings.field.dob.form.help.text'].defaultMessage);
|
||||||
|
expect(screen.getByTestId('month-label')).toHaveTextContent(messages['account.settings.field.dob.month'].defaultMessage);
|
||||||
|
expect(screen.getByTestId('year-label')).toHaveTextContent(messages['account.settings.field.dob.year'].defaultMessage);
|
||||||
|
expect(screen.getByTestId('month-select')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('year-select')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('cancel-button')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('submit-button')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables submit button when both month and year are selected', async () => {
|
||||||
|
renderComponent();
|
||||||
|
const openButton = screen.getByTestId('open-modal-button');
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(openButton);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
const monthSelect = screen.getByTestId('month-select');
|
||||||
|
const yearSelect = screen.getByTestId('year-select');
|
||||||
|
const submitButton = screen.getByTestId('submit-button');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(monthSelect, { target: { value: '6' } });
|
||||||
|
fireEvent.change(yearSelect, { target: { value: YEAR_OF_BIRTH_OPTIONS[0].value } });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(submitButton).not.toHaveAttribute('disabled');
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onSubmit with correct data when form is submitted', async () => {
|
||||||
|
const mockOnSubmit = jest.fn();
|
||||||
|
renderComponent({ onSubmit: mockOnSubmit });
|
||||||
|
|
||||||
|
const openButton = screen.getByTestId('open-modal-button');
|
||||||
|
await act(async () => {
|
||||||
|
fireEvent.click(openButton);
|
||||||
|
});
|
||||||
|
await waitFor(() => {
|
||||||
|
const monthSelect = screen.getByTestId('month-select');
|
||||||
|
const yearSelect = screen.getByTestId('year-select');
|
||||||
|
const form = screen.getByTestId('dob-form');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(monthSelect, { target: { value: '6' } });
|
||||||
|
fireEvent.change(yearSelect, { target: { value: '1990' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.submit(form);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOnSubmit).toHaveBeenCalledWith('extended_profile', [
|
||||||
|
{ field_name: 'DOB', field_value: '1990-6' },
|
||||||
|
]);
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
});
|
||||||
184
src/account-settings/test/EditableField.test.jsx
Normal file
184
src/account-settings/test/EditableField.test.jsx
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
render, screen, fireEvent, waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import EditableField from '../EditableField';
|
||||||
|
import messages from '../AccountSettingsPage.messages';
|
||||||
|
|
||||||
|
jest.mock('../data/selectors', () => ({
|
||||||
|
editableFieldSelector: () => (state, props) => ({
|
||||||
|
...state.accountSettings,
|
||||||
|
isEditing: props.isEditing,
|
||||||
|
error: props.error || state.accountSettings.errors[props.name],
|
||||||
|
confirmationValue: props.confirmationValue || state.accountSettings.confirmationValues[props.name],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../data/actions', () => ({
|
||||||
|
openForm: jest.fn((name) => ({ type: 'OPEN_FORM', payload: name })),
|
||||||
|
closeForm: jest.fn((name) => ({ type: 'CLOSE_FORM', payload: name })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
jest.mock('../certificate-preference/CertificatePreference', () => function MockCertificatePreference({ fieldName }) {
|
||||||
|
return <div data-testid="editable-field-certificate-preference">Certificate Preference for {fieldName}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockStore = configureStore([]);
|
||||||
|
const mockOnEdit = jest.fn();
|
||||||
|
const mockOnCancel = jest.fn();
|
||||||
|
const mockOnSubmit = jest.fn();
|
||||||
|
const mockOnChange = jest.fn();
|
||||||
|
|
||||||
|
const baseState = {
|
||||||
|
accountSettings: {
|
||||||
|
errors: {},
|
||||||
|
confirmationValues: {},
|
||||||
|
saveState: 'default',
|
||||||
|
openFormId: null,
|
||||||
|
verifiedNameHistory: { results: [] },
|
||||||
|
values: {},
|
||||||
|
drafts: {},
|
||||||
|
timeZones: [],
|
||||||
|
countryTimeZones: [],
|
||||||
|
thirdPartyAuth: { providers: [] },
|
||||||
|
countriesCodesList: [],
|
||||||
|
profileDataManager: false,
|
||||||
|
nameChangeModal: {},
|
||||||
|
loading: false,
|
||||||
|
loaded: true,
|
||||||
|
loadingError: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderComponent = (props = {}, stateOverrides = {}) => {
|
||||||
|
const store = mockStore({
|
||||||
|
...baseState,
|
||||||
|
...stateOverrides,
|
||||||
|
});
|
||||||
|
return render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<EditableField
|
||||||
|
name="username"
|
||||||
|
label="Username"
|
||||||
|
type="text"
|
||||||
|
value="john_doe"
|
||||||
|
onEdit={mockOnEdit}
|
||||||
|
onCancel={mockOnCancel}
|
||||||
|
onSubmit={mockOnSubmit}
|
||||||
|
onChange={mockOnChange}
|
||||||
|
isEditing={false}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
</Provider>,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('EditableField', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders default state with value', () => {
|
||||||
|
renderComponent();
|
||||||
|
expect(screen.getByText('Username')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('john_doe')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /Edit/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders empty label with edit button if no value and editable', () => {
|
||||||
|
renderComponent({ value: '', emptyLabel: 'Add value' });
|
||||||
|
expect(screen.getByRole('button', { name: 'Add value' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders empty label as muted text if not editable', () => {
|
||||||
|
renderComponent({ value: '', emptyLabel: 'No value', isEditable: false });
|
||||||
|
expect(screen.getByText('No value')).toHaveClass('text-muted');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders editing state with form controls', async () => {
|
||||||
|
renderComponent({ isEditing: true });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('editable-field-textbox')).toHaveValue('john_doe');
|
||||||
|
expect(screen.getByTestId('editable-field-save')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('editable-field-cancel')).toBeInTheDocument();
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onChange when input changes', async () => {
|
||||||
|
renderComponent({ isEditing: true });
|
||||||
|
await waitFor(() => {
|
||||||
|
const input = screen.getByTestId('editable-field-textbox');
|
||||||
|
fireEvent.change(input, { target: { value: 'new_name' } });
|
||||||
|
expect(mockOnChange).toHaveBeenCalledWith('username', 'new_name');
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onSubmit when form is submitted', async () => {
|
||||||
|
renderComponent({ isEditing: true });
|
||||||
|
await waitFor(() => {
|
||||||
|
const form = screen.getByTestId('editable-field-form');
|
||||||
|
fireEvent.submit(form);
|
||||||
|
expect(mockOnSubmit).toHaveBeenCalledWith('username', 'john_doe');
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows error message when error is present', async () => {
|
||||||
|
const stateOverrides = {
|
||||||
|
accountSettings: {
|
||||||
|
...baseState.accountSettings,
|
||||||
|
errors: { username: 'Invalid input' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
renderComponent({ isEditing: true, error: 'Invalid input' }, stateOverrides);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('editable-field-error')).toHaveTextContent('Invalid input');
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows help text in editing mode', () => {
|
||||||
|
renderComponent({ isEditing: true, helpText: 'Helpful info' });
|
||||||
|
expect(screen.getByText('Helpful info')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows confirmation message in default mode if provided', async () => {
|
||||||
|
const stateOverrides = {
|
||||||
|
accountSettings: {
|
||||||
|
...baseState.accountSettings,
|
||||||
|
confirmationValues: { username: 'done' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
renderComponent(
|
||||||
|
{
|
||||||
|
confirmationMessageDefinition: messages['account.settings.editable.field.action.save'],
|
||||||
|
confirmationValue: 'done',
|
||||||
|
},
|
||||||
|
stateOverrides,
|
||||||
|
);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('editable-field-confirmation')).toBeInTheDocument();
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders CertificatePreference for name fields when editing', async () => {
|
||||||
|
renderComponent({ isEditing: true, name: 'name' });
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('editable-field-certificate-preference')).toHaveTextContent('Certificate Preference for name');
|
||||||
|
}, { timeout: 2000 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('applies grayed-out class when isGrayedOut is true', () => {
|
||||||
|
renderComponent({ isGrayedOut: true });
|
||||||
|
expect(screen.getByText('john_doe')).toHaveClass('grayed-out');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('appends userSuppliedValue when provided', () => {
|
||||||
|
renderComponent({ userSuppliedValue: 'extra' });
|
||||||
|
expect(screen.getByText('john_doe: extra')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
166
src/account-settings/test/EditableSelectField.test.jsx
Normal file
166
src/account-settings/test/EditableSelectField.test.jsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import configureStore from 'redux-mock-store';
|
||||||
|
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
import EditableSelectField from '../EditableSelectField';
|
||||||
|
|
||||||
|
const mockDispatch = jest.fn();
|
||||||
|
jest.mock('react-redux', () => ({
|
||||||
|
...jest.requireActual('react-redux'),
|
||||||
|
useDispatch: () => mockDispatch,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@edx/frontend-platform/auth');
|
||||||
|
jest.mock('../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
|
||||||
|
|
||||||
|
const mockStore = configureStore();
|
||||||
|
|
||||||
|
describe('EditableSelectField', () => {
|
||||||
|
let props = {};
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
const reduxWrapper = children => (
|
||||||
|
<Router>
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStore();
|
||||||
|
props = {
|
||||||
|
name: 'testField',
|
||||||
|
label: 'Main Label',
|
||||||
|
emptyLabel: 'Empty Main Label',
|
||||||
|
type: 'text',
|
||||||
|
value: 'Test Field',
|
||||||
|
userSuppliedValue: '',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Default Option',
|
||||||
|
value: 'defaultOption',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'User Options',
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
label: 'Suboption 1',
|
||||||
|
value: 'suboption1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Other Options',
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
label: 'Suboption 2',
|
||||||
|
value: 'suboption2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Suboption 3',
|
||||||
|
value: 'suboption3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
saveState: 'default',
|
||||||
|
error: '',
|
||||||
|
confirmationMessageDefinition: {
|
||||||
|
id: 'confirmationMessageId',
|
||||||
|
defaultMessage: 'Default Confirmation Message',
|
||||||
|
description: 'Description of the confirmation message',
|
||||||
|
},
|
||||||
|
confirmationValue: 'Confirmation Value',
|
||||||
|
helpText: 'Helpful Text',
|
||||||
|
isEditing: false,
|
||||||
|
isEditable: true,
|
||||||
|
isGrayedOut: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
|
it('renders EditableSelectField correctly with editing disabled', () => {
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...props} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders EditableSelectField correctly with editing enabled', () => {
|
||||||
|
props = {
|
||||||
|
...props,
|
||||||
|
isEditing: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...props} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders EditableSelectField with an error', () => {
|
||||||
|
const errorProps = {
|
||||||
|
...props,
|
||||||
|
error: 'This is an error message',
|
||||||
|
};
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...errorProps} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders selectOptions when option has a group', () => {
|
||||||
|
const propsWithGroup = {
|
||||||
|
...props,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'User Options',
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
label: 'Suboption 1',
|
||||||
|
value: 'suboption1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithGroup} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders selectOptions when option does not have a group', () => {
|
||||||
|
const propsWithoutGroup = {
|
||||||
|
...props,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Default Option',
|
||||||
|
value: 'defaultOption',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithoutGroup} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders selectOptions with multiple groups', () => {
|
||||||
|
const propsWithGroups = {
|
||||||
|
...props,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Mixed Options',
|
||||||
|
group: [
|
||||||
|
{
|
||||||
|
label: 'Suboption 1',
|
||||||
|
value: 'suboption1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Suboption 2',
|
||||||
|
value: 'suboption2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const tree = renderer.create(reduxWrapper(<EditableSelectField {...propsWithGroups} />)).toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,62 +1,64 @@
|
|||||||
import React from 'react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import renderer from 'react-test-renderer';
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
|
import { AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
import { initializeMockApp, mergeConfig, setConfig } from '@edx/frontend-platform';
|
||||||
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { mergeConfig, setConfig } from '@edx/frontend-platform';
|
|
||||||
import JumpNav from '../JumpNav';
|
import JumpNav from '../JumpNav';
|
||||||
|
import configureStore from '../../data/configureStore';
|
||||||
const IntlJumpNav = injectIntl(JumpNav);
|
|
||||||
|
|
||||||
describe('JumpNav', () => {
|
describe('JumpNav', () => {
|
||||||
mergeConfig({
|
mergeConfig({
|
||||||
ENABLE_DEMOGRAPHICS_COLLECTION: false,
|
ENABLE_ACCOUNT_DELETION: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
let props = {};
|
let store;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
props = {
|
initializeMockApp({
|
||||||
intl: {},
|
authenticatedUser: {
|
||||||
displayDemographicsLink: false,
|
userId: 3,
|
||||||
};
|
username: 'abc123',
|
||||||
});
|
administrator: true,
|
||||||
|
roles: [],
|
||||||
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 = {
|
store = configureStore({
|
||||||
...props,
|
notificationPreferences: {
|
||||||
displayDemographicsLink: true,
|
showPreferences: false,
|
||||||
};
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const tree = renderer.create((
|
it('should not render delete account link', async () => {
|
||||||
// Same as previous test
|
setConfig({
|
||||||
<Router>
|
ENABLE_ACCOUNT_DELETION: false,
|
||||||
<IntlProvider locale="en">
|
});
|
||||||
<IntlJumpNav {...props} />
|
|
||||||
</IntlProvider>
|
|
||||||
</Router>
|
|
||||||
))
|
|
||||||
.toJSON();
|
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<AppProvider store={store}>
|
||||||
|
<JumpNav />
|
||||||
|
</AppProvider>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.queryByText('Delete My Account')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render delete account link', async () => {
|
||||||
|
setConfig({
|
||||||
|
ENABLE_ACCOUNT_DELETION: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<AppProvider store={store}>
|
||||||
|
<JumpNav />
|
||||||
|
</AppProvider>
|
||||||
|
</IntlProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await screen.findByText('Delete My Account')).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user