Compare commits
522 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9afb2e7c0 | |||
|
|
f7d7d4a25d | ||
|
|
8a5ba98e96 | ||
|
|
29e96e3b5a | ||
|
|
1abf704144 | ||
|
|
9905fff1a0 | ||
|
|
c63030a52d | ||
|
|
08445d891d | ||
|
|
1dc8f3e654 | ||
|
|
0f56f46109 | ||
|
|
8b9246efe7 | ||
|
|
b1335b9373 | ||
|
|
bbe3b58620 | ||
|
|
c99804bff4 | ||
|
|
2d03191631 | ||
|
|
c23d3fe74d | ||
|
|
1d12c6506e | ||
|
|
39a88243e5 | ||
|
|
9466f6aced | ||
|
|
7cb75a333f | ||
|
|
4dcbae37f2 | ||
|
|
636e71e141 | ||
|
|
320cfa17a2 | ||
|
|
b61302c3f5 | ||
|
|
e6444392a6 | ||
|
|
cd10265c60 | ||
|
|
c7a84e62f1 | ||
|
|
2d3c5ecc4c | ||
|
|
b6c1f77331 | ||
|
|
a3ef54e2bf | ||
|
|
bb69baecb0 | ||
|
|
95e191cf86 | ||
|
|
61702da769 | ||
|
|
47d467747f | ||
|
|
75bd146e72 | ||
|
|
db201af910 | ||
|
|
281b817084 | ||
|
|
08b69b8096 | ||
|
|
7c8876490d | ||
|
|
ba2e46bfa9 | ||
|
|
09cc332306 | ||
|
|
1f91a60b6d | ||
|
|
490da867df | ||
|
|
fd723e6063 | ||
|
|
664ca3e78f | ||
|
|
18a8a76105 | ||
|
|
0ad89f88a1 | ||
|
|
b0391866a3 | ||
|
|
3a9e3f8fba | ||
|
|
b2c55a09e5 | ||
|
|
6795147255 | ||
|
|
a87c111b3c | ||
|
|
655d6c058b | ||
|
|
923009191f | ||
|
|
80da098ec8 | ||
|
|
0197c85de5 | ||
|
|
0ef407458c | ||
|
|
c5d83f658a | ||
|
|
eb941181ea | ||
|
|
ed379258a8 | ||
|
|
8109ba39bb | ||
|
|
ebb87070da | ||
|
|
cbcaf4c9fe | ||
|
|
d3dda9e56d | ||
|
|
db3c54201c | ||
|
|
8f8b4645b4 | ||
|
|
194f93e85e | ||
|
|
10fc7a2d72 | ||
|
|
1d7a871cfd | ||
|
|
cf09847e95 | ||
|
|
84e30f0fb7 | ||
|
|
288b6c7d0f | ||
|
|
9a23003f7f | ||
|
|
922a31b90b | ||
|
|
73236b296b | ||
|
|
eb92d8f1f1 | ||
|
|
bc79936022 | ||
|
|
2aac71a988 | ||
|
|
032105da7f | ||
|
|
6e2ccf2f2c | ||
|
|
907145be24 | ||
|
|
58791757d7 | ||
|
|
583aaf676e | ||
|
|
2e46ec1f9b | ||
|
|
bb80d92512 | ||
|
|
e138dedd29 | ||
|
|
073a6c2a18 | ||
|
|
41cf64b9d7 | ||
|
|
908b5b0b11 | ||
|
|
59dd82c919 | ||
|
|
afed581a18 | ||
|
|
f6b02cbfe7 | ||
|
|
ede01dbd20 | ||
|
|
4a0f244a1a | ||
|
|
906d9a78c1 | ||
|
|
ac0d5f42f7 | ||
|
|
e9d1b435dd | ||
|
|
cc647e7bc8 | ||
|
|
2d5f5fe787 | ||
|
|
ef7bd9284a | ||
|
|
10f9d7c1cd | ||
|
|
6b3a49ccec | ||
|
|
f473c98bb8 | ||
|
|
bd52be3177 | ||
|
|
ef12ac20fe | ||
|
|
7fa6bd8740 | ||
|
|
8d9178f532 | ||
|
|
98bcacfced | ||
|
|
ac78454133 | ||
|
|
09b7c8be9a | ||
|
|
c992a9129a | ||
|
|
7f6ad73a25 | ||
|
|
cefbd28d9b | ||
|
|
c26bdc54e1 | ||
|
|
02f6b44368 | ||
|
|
4cbde7e38f | ||
|
|
bdb6ecb79f | ||
|
|
46c1ffd731 | ||
|
|
b6bd211c6e | ||
|
|
ef8445a1b2 | ||
|
|
5b34595520 | ||
|
|
32ae2d8e7c | ||
|
|
abac1cd6d4 | ||
|
|
e0115e3d88 | ||
|
|
ff18f3add9 | ||
|
|
4926c3105b | ||
|
|
838a81a020 | ||
|
|
0ac6840cea | ||
|
|
26a17bf2e4 | ||
|
|
17b9710810 | ||
|
|
df0129485e | ||
|
|
1c2f290faa | ||
|
|
9af9024bcc | ||
|
|
0008bd485a | ||
|
|
181b9916a0 | ||
|
|
22dbec317c | ||
|
|
6f15e4dafa | ||
|
|
9f5aed3c76 | ||
|
|
54bc04b182 | ||
|
|
ea2578fa15 | ||
|
|
5f7fab236d | ||
|
|
adf5f6c9a8 | ||
|
|
132b080195 | ||
|
|
0d4ea96874 | ||
|
|
8b9f5fa348 | ||
|
|
c014e30ca8 | ||
|
|
0e6ab3c358 | ||
|
|
7d0a126580 | ||
|
|
c422ce40be | ||
|
|
7299d5367f | ||
|
|
38203b91ba | ||
|
|
83c340fe08 | ||
|
|
9eaebf4b11 | ||
|
|
2a883857cf | ||
|
|
5f26c1ae93 | ||
|
|
89ad7234ac | ||
|
|
936915f81f | ||
|
|
b993992987 | ||
|
|
64fc8adeed | ||
|
|
a49b06349e | ||
|
|
aa13b4946c | ||
|
|
cff78e021c | ||
|
|
62bf038a06 | ||
|
|
eed127969d | ||
|
|
dcce076942 | ||
|
|
6cf8bf3965 | ||
|
|
fad5b0925e | ||
|
|
1f5e5fa4dd | ||
|
|
a94f57dae2 | ||
|
|
531ae4a603 | ||
|
|
2a2f9ed43f | ||
|
|
5c0ac3de54 | ||
|
|
4aec12eae0 | ||
|
|
5059e0dac1 | ||
|
|
ed5aeb8179 | ||
|
|
19fe6680a9 | ||
|
|
59c78ac479 | ||
|
|
238854bdfb | ||
|
|
07ad578613 | ||
|
|
fc3c9581f3 | ||
|
|
0015b72eb7 | ||
|
|
f694e322ee | ||
|
|
5e7ac40348 | ||
|
|
c2e5f0f403 | ||
|
|
d0b9a40f5d | ||
|
|
3f815b386f | ||
|
|
e9bb918925 | ||
|
|
9b1a3e994a | ||
|
|
7253f9de2b | ||
|
|
3e6ccb2ea1 | ||
|
|
7c3c925838 | ||
|
|
cde76129af | ||
|
|
29050d109f | ||
|
|
5caf0ea549 | ||
|
|
3571e5c3ed | ||
|
|
1011854f51 | ||
|
|
a5859702b5 | ||
|
|
2b3a13a5a0 | ||
|
|
ca4b4905b7 | ||
|
|
c7e42fda3d | ||
|
|
2266486398 | ||
|
|
91cd859ffc | ||
|
|
c5e8a12f89 | ||
|
|
edd4cc4589 | ||
|
|
6202398292 | ||
|
|
f8c26fc369 | ||
|
|
63656116c5 | ||
|
|
e2ccd1b56a | ||
|
|
c0f4b90505 | ||
|
|
c3f5a07256 | ||
|
|
7450b8f061 | ||
|
|
677d72813e | ||
|
|
abe24fb46a | ||
|
|
ce5396c9ab | ||
|
|
dc0982dfd2 | ||
|
|
a9f6ca8b77 | ||
|
|
d2e790ce99 | ||
|
|
f1bd7d2ec2 | ||
|
|
425616defe | ||
|
|
fca4a6bd29 | ||
|
|
bdaaa8541f | ||
|
|
0fce9cd9f7 | ||
|
|
e4adba4f66 | ||
|
|
be8529246c | ||
|
|
98f818e295 | ||
|
|
fb869bc008 | ||
|
|
879f384809 | ||
|
|
b65a80616b | ||
|
|
d5f0bca2b7 | ||
|
|
3e6eaa1ded | ||
|
|
48b56df115 | ||
|
|
1d2bfd8e40 | ||
|
|
197f24a684 | ||
|
|
0d6c99acbc | ||
|
|
8213c3a66a | ||
|
|
a6ae384f7e | ||
|
|
dd612879e2 | ||
|
|
248bab9370 | ||
|
|
92c879bf8f | ||
|
|
16275a2ecc | ||
|
|
970ca113e9 | ||
|
|
65c459c53e | ||
|
|
961faafd3a | ||
|
|
16b468bdcc | ||
|
|
91d6ea6572 | ||
|
|
e13c7a6349 | ||
|
|
ec288a6430 | ||
|
|
10335c8075 | ||
|
|
e09f1af861 | ||
|
|
c1e5885ecb | ||
|
|
7849899658 | ||
|
|
c508632126 | ||
|
|
ef148e8d35 | ||
|
|
b8a162f9c9 | ||
|
|
5643296f04 | ||
|
|
5cf89420b3 | ||
|
|
176eb22711 | ||
|
|
4380dd2a14 | ||
|
|
a0080cd4ef | ||
|
|
1f80f98acc | ||
|
|
d7ed4bfaf2 | ||
|
|
898e23d4bd | ||
|
|
7c07480f31 | ||
|
|
f956c0ac93 | ||
|
|
d625329324 | ||
|
|
01ccaa6988 | ||
|
|
c0dd43ce4a | ||
|
|
56469df0b5 | ||
|
|
0de4ad621e | ||
|
|
5d50e59672 | ||
|
|
c715f0c4d4 | ||
|
|
a62124ddf2 | ||
|
|
8c222ddb0a | ||
|
|
b4031ce345 | ||
|
|
b787f5ecfe | ||
|
|
f87d47f42d | ||
|
|
818ed5d179 | ||
|
|
07eb5892da | ||
|
|
3b8b18051f | ||
|
|
1c719d63cb | ||
|
|
dc7d2fadff | ||
|
|
b710800b2a | ||
|
|
74ff35ae9a | ||
|
|
e6835f9c56 | ||
|
|
a0c19faa92 | ||
|
|
fa37ebb828 | ||
|
|
a24972b64f | ||
|
|
abb22a82ba | ||
|
|
1df0b94a8c | ||
|
|
d563e2f444 | ||
|
|
92a210f81d | ||
|
|
1b92a3c505 | ||
|
|
f88d47275b | ||
|
|
26f3900602 | ||
|
|
a9db712240 | ||
|
|
739cce6c50 | ||
|
|
3a19e219bb | ||
|
|
12511689e8 | ||
|
|
f084393cc9 | ||
|
|
4035ff122c | ||
|
|
159b5a842e | ||
|
|
b06c8b5d01 | ||
|
|
c79da49208 | ||
|
|
82b3310a8f | ||
|
|
fcd641259b | ||
|
|
509276df12 | ||
|
|
4527baef5a | ||
|
|
8183143ae5 | ||
|
|
91311f25da | ||
|
|
ccef5de855 | ||
|
|
01fa97055f | ||
|
|
3cff0970a3 | ||
|
|
f2c879999d | ||
|
|
3f18cf7c9d | ||
|
|
30796204b8 | ||
|
|
d206ecf3ad | ||
|
|
f3094e1725 | ||
|
|
5bbd266834 | ||
|
|
37eed0578f | ||
|
|
acbe489a7c | ||
|
|
2cfbb40265 | ||
|
|
3e1264b710 | ||
|
|
bf60da6554 | ||
|
|
cf879e53be | ||
|
|
e0a634900d | ||
|
|
15ae025990 | ||
|
|
efd85745fd | ||
|
|
fc5df4af36 | ||
|
|
f657c719ec | ||
|
|
6c346c18f9 | ||
|
|
2b9c5affbc | ||
|
|
4a13120d24 | ||
|
|
8e0587f871 | ||
|
|
7cc2f74562 | ||
|
|
3b13fc29b4 | ||
|
|
3f12c52221 | ||
|
|
826d071566 | ||
|
|
b66ae651b6 | ||
|
|
d23a4e2443 | ||
|
|
898b90eb07 | ||
|
|
dbb806d0a3 | ||
|
|
702bce8587 | ||
|
|
0bd6c9d452 | ||
|
|
3d4d356665 | ||
|
|
e656865502 | ||
|
|
2b4283ac5f | ||
|
|
38d9a2076b | ||
|
|
a31d1a5387 | ||
|
|
a374cee7dd | ||
|
|
3f50a4d333 | ||
|
|
45f0f20f0f | ||
|
|
fb06f7d9f9 | ||
|
|
29a5db5e1a | ||
|
|
7982251640 | ||
|
|
9824aecd16 | ||
|
|
dcc361747e | ||
|
|
863ecb41bd | ||
|
|
f6d02d177d | ||
|
|
7e196f48cf | ||
|
|
46e4c78af8 | ||
|
|
affbcbeb19 | ||
|
|
8c10919eb9 | ||
|
|
314781ac0b | ||
|
|
b75ab299bd | ||
|
|
1fb4475b9a | ||
|
|
2b56d270a2 | ||
|
|
395c9240a0 | ||
|
|
60ceedced9 | ||
|
|
032e4dd73f | ||
|
|
212bc015ad | ||
|
|
d72eaa6fab | ||
|
|
1f5b74de21 | ||
|
|
07728e1498 | ||
|
|
5d44307136 | ||
|
|
1769069cfe | ||
|
|
2fb0cac399 | ||
|
|
807dc66714 | ||
|
|
ea837695e9 | ||
|
|
e4801b8b81 | ||
|
|
89337c4819 | ||
|
|
c75f6c079e | ||
|
|
77c251467c | ||
|
|
caba23d51b | ||
|
|
efbae26c56 | ||
|
|
efeacb8b73 | ||
|
|
a0865c9c56 | ||
|
|
7cfd6932d5 | ||
|
|
e2f92992b6 | ||
|
|
f37e6c164e | ||
|
|
fef30fb8b5 | ||
|
|
bcac04d0cc | ||
|
|
15ff0203ab | ||
|
|
940294d2a0 | ||
|
|
acd4a843a4 | ||
|
|
d8ee5425bf | ||
|
|
1d9e8d3c47 | ||
|
|
4d370f8a75 | ||
|
|
2a2be8eddb | ||
|
|
e7624adbfb | ||
|
|
5d0a418e85 | ||
|
|
f5b18cdd54 | ||
|
|
d5d290bcbe | ||
|
|
49c02c157b | ||
|
|
94b4aa7402 | ||
|
|
a9fe23287f | ||
|
|
30874b4972 | ||
|
|
14d53ef514 | ||
|
|
5c9270d88a | ||
|
|
f70ca27b34 | ||
|
|
d247e88541 | ||
|
|
ea257afcca | ||
|
|
24603cf98f | ||
|
|
4249bbc6df | ||
|
|
611beb8294 | ||
|
|
87803604b1 | ||
|
|
99344d93a4 | ||
|
|
2a718743ea | ||
|
|
4fe8909ad5 | ||
|
|
a0dc37dfb5 | ||
|
|
e4917030fa | ||
|
|
c756f42c28 | ||
|
|
1b6a5eda0b | ||
|
|
cbaf4bbb9e | ||
|
|
c990cbbf5e | ||
|
|
b41af87b8e | ||
|
|
a7d59a874c | ||
|
|
d66d3b7b62 | ||
|
|
7095e79471 | ||
|
|
4a9b3f2bfb | ||
|
|
e4d23d366b | ||
|
|
2e75cc95cb | ||
|
|
f64faf9f14 | ||
|
|
0be3098191 | ||
|
|
799cdb3b3c | ||
|
|
370ae52c04 | ||
|
|
5037a5610b | ||
|
|
0a91f8242f | ||
|
|
457bd843cf | ||
|
|
777c697c41 | ||
|
|
b3b32613c9 | ||
|
|
a6f70e895a | ||
|
|
1112b426f0 | ||
|
|
17278cc355 | ||
|
|
65c25bfc95 | ||
|
|
75e8962563 | ||
|
|
65aba84491 | ||
|
|
093dfa6ac2 | ||
|
|
4637834750 | ||
|
|
a021e72359 | ||
|
|
925ddb7adf | ||
|
|
9ebcd15623 | ||
|
|
3a2e553feb | ||
|
|
eb6c253e35 | ||
|
|
d2315c2c21 | ||
|
|
f33a972933 | ||
|
|
d540aaa390 | ||
|
|
c64496dd08 | ||
|
|
e94cf3ac2d | ||
|
|
d4259ef1f6 | ||
|
|
b4f35d37c8 | ||
|
|
83bad3d47b | ||
|
|
31e255ebe9 | ||
|
|
349f83f686 | ||
|
|
e0c205ce14 | ||
|
|
81398c8cfd | ||
|
|
3fcd76b455 | ||
|
|
a8f57aa553 | ||
|
|
f32b0c8dcf | ||
|
|
fb1f5c2d50 | ||
|
|
1a51d992cd | ||
|
|
e9c28163d9 | ||
|
|
6db2d70e97 | ||
|
|
3ab4adf10b | ||
|
|
a482325eac | ||
|
|
d919d1a577 | ||
|
|
4b3a91138d | ||
|
|
7680ad3393 | ||
|
|
061b41bee8 | ||
|
|
cacae4789f | ||
|
|
adceca6b53 | ||
|
|
ab61fd35eb | ||
|
|
4d739f2962 | ||
|
|
28a6c85bc0 | ||
|
|
6125988bbd | ||
|
|
9eeccc63cb | ||
|
|
2f33c6718b | ||
|
|
89397fcfdb | ||
|
|
278b4b1473 | ||
|
|
7ff9039bb7 | ||
|
|
0cf3164901 | ||
|
|
bf8020dfac | ||
|
|
f2a13b8c8e | ||
|
|
3dd9b25d11 | ||
|
|
21d7fe04e3 | ||
|
|
46d464d515 | ||
|
|
85ed8fbff4 | ||
|
|
d6adfec189 | ||
|
|
8b1943356e | ||
|
|
5aae6e8336 | ||
|
|
ae18ad45b9 | ||
|
|
0e15bb7e55 | ||
|
|
ff664b9d3b | ||
|
|
da91fb6392 | ||
|
|
f265c7daa6 | ||
|
|
3037e4d8a6 | ||
|
|
b2a7696c0e | ||
|
|
6dc8f64ccb | ||
|
|
0453d7fbaa | ||
|
|
fa6d38f422 | ||
|
|
9f3e945d4e | ||
|
|
429d19291d | ||
|
|
e27bc8de39 | ||
|
|
cd0b84c223 | ||
|
|
26810c99e6 | ||
|
|
2fb8ea2608 | ||
|
|
16c6da229b | ||
|
|
fb8de7a59c | ||
|
|
21a9e6c5e1 | ||
|
|
9475888392 | ||
|
|
c069f20716 | ||
|
|
46772ecc0e | ||
|
|
ca09a07c62 |
27
.babelrc
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["env", {
|
|
||||||
"targets": {
|
|
||||||
"browsers": ["last 2 versions", "ie 11"]
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"babel-preset-react"
|
|
||||||
],
|
|
||||||
"plugins": [
|
|
||||||
"transform-object-rest-spread"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"test": {
|
|
||||||
"plugins": [
|
|
||||||
"rewire"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"i18n": {
|
|
||||||
"plugins": [
|
|
||||||
["react-intl", {
|
|
||||||
"messagesDir": "./temp"
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
.env.development
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
|
||||||
|
BASE_URL=localhost:8080
|
||||||
|
CREDENTIALS_BASE_URL=http://localhost:18150
|
||||||
|
CSRF_TOKEN_API_PATH=/csrf/api/v1/token
|
||||||
|
ECOMMERCE_BASE_URL=http://localhost:18130
|
||||||
|
LANGUAGE_PREFERENCE_COOKIE_NAME=openedx-language-preference
|
||||||
|
LMS_BASE_URL=http://localhost:18000
|
||||||
|
LOGIN_URL=http://localhost:18000/login
|
||||||
|
LOGOUT_URL=http://localhost:18000/login
|
||||||
|
MARKETING_SITE_BASE_URL=http://localhost:18000
|
||||||
|
TERMS_OF_SERVICE_URL=null
|
||||||
|
PRIVACY_POLICY_URL=null
|
||||||
|
SUPPORT_EMAIL=null
|
||||||
|
STUDIO_BASE_URL=http://localhost:18010
|
||||||
|
ENABLE_ACCESSIBILITY_PAGE=false
|
||||||
|
ORDER_HISTORY_URL=localhost:1996/orders
|
||||||
|
REFRESH_ACCESS_TOKEN_ENDPOINT=http://localhost:18000/login_refresh
|
||||||
|
SEGMENT_KEY=null
|
||||||
|
SITE_NAME=Open edX
|
||||||
|
USER_INFO_COOKIE_NAME=edx-user-info
|
||||||
|
LOGO_URL=https://edx-cdn.org/v3/default/logo.svg
|
||||||
|
LOGO_TRADEMARK_URL=https://edx-cdn.org/v3/default/logo-trademark.svg
|
||||||
|
LOGO_WHITE_URL=https://edx-cdn.org/v3/default/logo-white.svg
|
||||||
|
FAVICON_URL=https://edx-cdn.org/v3/default/favicon.ico
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
coverage
|
coverage
|
||||||
dist
|
dist
|
||||||
|
example
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
4
.eslintrc.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
|
module.exports = createConfig('eslint');
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "eslint-config-edx",
|
|
||||||
"rules": {
|
|
||||||
"import/no-extraneous-dependencies": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"devDependencies": [
|
|
||||||
"webpack.config.js",
|
|
||||||
"src/tests/setupTest.js",
|
|
||||||
"**/*.test.jsx",
|
|
||||||
"**/*.test.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"jest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
.github/workflows/add-depr-ticket-to-depr-board.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Run the workflow that adds new tickets that are either:
|
||||||
|
# - labelled "DEPR"
|
||||||
|
# - title starts with "[DEPR]"
|
||||||
|
# - body starts with "Proposal Date" (this is the first template field)
|
||||||
|
# to the org-wide DEPR project board
|
||||||
|
|
||||||
|
name: Add newly created DEPR issues to the DEPR project board
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
routeissue:
|
||||||
|
uses: openedx/.github/.github/workflows/add-depr-ticket-to-depr-board.yml@master
|
||||||
|
secrets:
|
||||||
|
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
|
||||||
|
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
|
||||||
|
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
|
||||||
20
.github/workflows/add-remove-label-on-comment.yml
vendored
Normal file
@@ -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
|
||||||
|
|
||||||
37
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Default CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Nodejs
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Validate package-lock.json changes
|
||||||
|
run: make validate-no-uncommitted-package-lock-changes
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
- name: Test
|
||||||
|
run: npm run test
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
- name: i18n_extract
|
||||||
|
run: npm run i18n_extract
|
||||||
|
- name: Coverage
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
fail_ci_if_error: true
|
||||||
10
.github/workflows/commitlint.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Run commitlint on the commit messages in a pull request.
|
||||||
|
|
||||||
|
name: Lint Commit Messages
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
commitlint:
|
||||||
|
uses: openedx/.github/.github/workflows/commitlint.yml@master
|
||||||
13
.github/workflows/lockfileversion-check.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#check package-lock file version
|
||||||
|
|
||||||
|
name: Lockfile Version check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
version-check:
|
||||||
|
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||||
45
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Release CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- alpha
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
|
contents: write # For Semantic Release tagging
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Validate package-lock.json changes
|
||||||
|
run: make validate-no-uncommitted-package-lock-changes
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
- name: Test
|
||||||
|
run: npm run test
|
||||||
|
- name: i18n_extract
|
||||||
|
run: npm run i18n_extract
|
||||||
|
- name: Coverage
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
fail_ci_if_error: false
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
- name: Release
|
||||||
|
run: npx semantic-release@25
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.OPENEDX_SEMANTIC_RELEASE_GITHUB_TOKEN }}
|
||||||
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
|
||||||
7
.gitignore
vendored
@@ -1,6 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
coverage
|
coverage
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
temp
|
temp
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
src/i18n/transifex_input.json
|
||||||
|
temp/babel-plugin-react-intl
|
||||||
|
/.vscode
|
||||||
|
module.config.js
|
||||||
|
src/i18n/messages
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"branch": "master",
|
"branches": [
|
||||||
|
"master",
|
||||||
|
{name: "alpha", prerelease: true}
|
||||||
|
],
|
||||||
"tagFormat": "v${version}",
|
"tagFormat": "v${version}",
|
||||||
"verifyConditions": [
|
"verifyConditions": [
|
||||||
"@semantic-release/npm",
|
"@semantic-release/npm",
|
||||||
|
|||||||
19
.travis.yml
@@ -1,19 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- 8
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- "~/.npm"
|
|
||||||
install:
|
|
||||||
- npm install
|
|
||||||
script:
|
|
||||||
- npm run lint
|
|
||||||
- npm run test
|
|
||||||
- npm run build
|
|
||||||
after_success:
|
|
||||||
- npm run travis-deploy-once "npm run semantic-release"
|
|
||||||
- npm run coveralls
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- secure: dxQadqP6tsoJzHcqs/Hs5AjE42z45q8ZeWKP5HcjbXoJURB4gc1uIxLky0FA6ZpulaTgRVTLcWQbx9yOODc9PQuFnFEDWlCg5EP8tONzeu7BVlJvV5eakgGUhl9w2pekBKsTGhK5dDg2y2D8bGfIL55UX81uiWeytp8s/y8QNs/FNXx9ScJnfhnC+2RfW52fB7iW12F1VYdQfVe43o5PsHze+YhB3FU/ztGe3iMaQiq9QplZWpvqQMpI7pTjyUAX8ITiiPS6UvLFObgpXpfjZdgd+yveFoi3z8o8F0NkmzBphFeSYFjFZE0qJ8bnGNIZldanMeuUgHmDeTwVmKQFhH2LqqnfcdGgW6UsKcHkSN1G51zzad2dEwAHrgxj1NkMp3JfEed2C7Kvntl6KRjVDmYZqHJvt+e+AHNbpjzblOW8tYMIrdz0TeJdk4D9pP3B3tRCtP6fvQ3GLzAMnaCrSsN6hZ9YVxWku8sg8WNEDHl14sZsdgk312MlHIdiUw97FHGrqx/NCix4IkUlCBDbKYbKzbZp20FfzZcwNRNH74+k6xpOnMGSfq8gByEhm9y02MBL76HiAI2VGct2La1ExaUfoikYGoNaZpFcZyOZKo6PYTYHpiUJmqrEnDyVQEOOXUaVsxWXwnYq/mU4nOEPKCRbNpPoksZdNxf6jlmMi8s=
|
|
||||||
- secure: lrlV0WQaXTRJ2lqDkFZ+1RkRb7YM/STOViOHUNboXb5+1ReVwY0wklFDVk/Qigp3jkbqWfzkBmEjSDVGDLD61QjpGY4BsxZ/Jn3+KUdo0n82Ym6cI/je1fH2gqDLi4U8bylmnkI5oEjV/1txDxkj4hF5w/Leo/oGue3xQohpi//ihhm/PxzExj+QiDqyZPZ5RQZeqLfPEU3Wff04vLE5bRmy1nDTZrgm5Wb1n5ItGsyrUyrCGuAM9kIEun65Snb8hxCuU9pSm1w/xF73iOGLiiC8KZhLu6SxSKoC872ai1GpUXNIqA8kpVeH0Erf5opMtqJT3jTTan/VOFQEoOeAKhR0ga+5rfK2jkhhN77B98dGVndNCfBpDlgQxVv42H6riFk3payZT262QiUqDejiBDtSPTokTGxf7xFtQkQQahhxVXzC2HRKEkDTXNSP8cvk2JJ4zCcUgxJpycudLMuC/Xv0upK+Q0caItBrHxfVNnRKkjKqlDxRhA8nXTY8d2n19FNi7wahCECbyweJJ76EaJaa/Ib6remUBrbGLoQ2PkaSMBHAcn3+7+H/6x11b2s1RHj5qyfIyrvZcDDyNuxdXwpOkhtrkwZsjgtOfXL6IuxW5FExgPPr8B9nNwJJKdTxyxgfqtwBR5+m9nrMixzT6AMe95torZ6eX40gKZ/O9EU=
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
|
|
||||||
[edx-platform.frontend-component-footer]
|
|
||||||
file_filter = "src/i18n/messages/<lang>.json"
|
|
||||||
source_file = "src/i18n/transifex_input.json"
|
|
||||||
source_lang = en
|
|
||||||
type = KEYVALUEJSON
|
|
||||||
17
Complete Logo.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<svg width="518" height="87" viewBox="0 0 518 87" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M512.654 43.4098C512.654 35.747 509.327 31.2325 503.564 31.2325C497.861 31.2325 494.474 35.747 494.474 43.4098C494.474 51.0726 497.861 55.5871 503.564 55.5871C509.327 55.5871 512.654 51.0726 512.654 43.4098ZM518 56.0029C518 67.111 513.188 72.3978 503.029 72.3978C499.286 72.3978 496.019 71.8632 492.87 70.6751V65.5072C496.019 67.1704 499.049 68.0021 502.198 68.0021C509.148 68.0021 512.654 64.3786 512.654 56.9534V54.3397C510.455 58.1414 507.01 60.0422 502.257 60.0422C498.277 60.0422 495.009 58.5572 492.573 55.5277C490.137 52.4982 488.949 48.4589 488.949 43.4098C488.949 38.3607 490.137 34.3807 492.573 31.3513C495.009 28.3218 498.277 26.7773 502.257 26.7773C507.01 26.7773 510.455 28.6782 512.654 32.4799V27.5496H518V56.0029Z" fill="black"/>
|
||||||
|
<path d="M484.03 50.3004C484.149 55.7653 484.209 59.864 484.209 59.864L478.743 59.9234C478.743 59.9234 478.862 60.7551 478.683 51.0726C478.565 45.4294 478.565 40.6179 478.565 40.6179C478.565 34.4401 476.129 31.4107 471.376 31.4107C465.613 31.4107 462.049 35.3312 462.049 41.6871V60.0422H456.702C456.702 60.0422 456.702 46.7363 456.702 37.1726C456.702 32.5987 453.375 33.0145 453.375 33.0145V27.5496C453.375 27.5496 456.88 27.5496 458.841 29.688C460.564 31.5889 460.623 33.7273 460.623 33.7273C461.811 29.7474 466.802 26.7773 472.03 26.7773C479.456 26.7773 483.911 31.4701 483.911 40.4397C483.911 40.4397 483.911 41.5089 484.03 50.3004Z" fill="black"/>
|
||||||
|
<path d="M443.611 27.5485H448.958C448.958 27.5485 448.661 42.8147 448.779 51.4873C448.779 55.5861 452.166 55.1702 452.166 55.1702V60.2194C452.166 60.2194 443.492 60.0412 443.611 52.2002C443.73 42.6365 443.611 27.5485 443.611 27.5485ZM446.225 14.3613C444.264 14.3613 442.66 15.9652 442.66 17.9254C442.66 19.8857 444.264 21.4895 446.225 21.4895C448.185 21.4895 449.789 19.9451 449.789 17.9254C449.789 15.9058 448.185 14.3613 446.225 14.3613Z" fill="black"/>
|
||||||
|
<path d="M436.376 50.3004C436.495 55.7653 436.554 59.864 436.554 59.864L431.089 59.9234C431.089 59.9234 431.207 60.7551 431.029 51.0726C430.91 45.4294 430.91 40.6179 430.91 40.6179C430.91 34.4401 428.475 31.4107 423.722 31.4107C417.959 31.4107 414.395 35.3312 414.395 41.6871V60.0422H409.048C409.048 60.0422 409.048 46.7363 409.048 37.1726C409.048 32.5987 405.721 33.0145 405.721 33.0145V27.5496C405.721 27.5496 409.226 27.5496 411.186 29.688C412.909 31.5889 412.969 33.7273 412.969 33.7273C414.157 29.7474 419.147 26.7773 424.375 26.7773C431.801 26.7773 436.257 31.4701 436.257 40.4397C436.257 40.4397 436.257 41.5089 436.376 50.3004Z" fill="black"/>
|
||||||
|
<path d="M403.931 32.4208C402.208 32.5396 400.961 32.5396 399 33.49C396.386 34.7969 393.237 37.7076 393.237 42.9349C393.237 49.8849 393.237 60.0426 393.237 60.0426H387.891V27.5499H393.237V32.599C395.257 29.5695 397.337 27.3717 401.911 27.1935C402.684 27.1341 402.446 27.1341 403.931 27.1935V32.4208Z" fill="black"/>
|
||||||
|
<path d="M369.696 43.7068C365.359 43.7068 362.388 44.182 360.725 45.1918C359.061 46.2017 358.23 47.8649 358.23 50.241C358.23 54.0427 360.903 56.4187 365.181 56.4187C371.122 56.4187 374.983 51.8448 374.983 44.8948V43.7068H369.696ZM380.271 51.9636C380.271 56.1811 384.251 55.8247 384.251 55.8247V59.9234C384.251 59.9234 381.221 60.2799 379.142 58.6166C377.241 57.1316 376.112 54.9337 376.112 54.9337C373.676 58.9136 369.042 60.8739 363.755 60.8739C357.101 60.8739 352.883 56.8346 352.883 50.5974C352.883 43.291 357.814 39.5487 367.498 39.5487H374.983V39.0141C374.983 34.0837 372.191 31.2919 366.309 31.2919C363.577 31.2919 360.071 33.3709 360.19 36.935L355.259 37.0538C355.378 28.6782 362.923 26.7773 366.428 26.7773C375.577 26.7773 380.33 31.5889 380.33 41.5089C380.33 41.5089 380.211 47.8055 380.271 51.9636Z" fill="black"/>
|
||||||
|
<path d="M349.423 42.4594V45.073H324.887C325.362 52.4388 329.402 56.3593 336.472 56.3593C340.571 56.3593 344.374 55.4089 348.176 53.3299V58.379C344.374 60.0422 340.274 60.8739 336.175 60.8739C331.006 60.8739 326.848 59.3888 323.818 56.3593C320.788 53.3299 319.303 49.2906 319.303 44.1226C319.303 38.8359 320.729 34.6184 323.58 31.4701C326.491 28.3218 330.353 26.7773 335.224 26.7773C339.621 26.7773 343.067 28.203 345.621 30.9949C348.176 33.7867 349.423 37.6478 349.423 42.4594ZM344.076 40.9149C344.017 35.0342 340.571 31.2919 335.284 31.2919C329.343 31.2919 325.6 34.7966 325.065 40.9149H344.076Z" fill="black"/>
|
||||||
|
<path d="M308.35 14.897H313.696C313.696 14.897 313.875 38.4794 313.875 51.1914C313.875 55.4089 319.459 55.3495 319.459 55.3495L319.4 60.161C319.4 60.161 313.578 60.3392 310.488 56.0029C309.003 53.9832 308.35 51.726 308.35 48.7559C308.35 35.3905 308.35 14.897 308.35 14.897Z" fill="black"/>
|
||||||
|
<path d="M279.439 14.897H284.786C284.786 14.897 284.965 38.4794 284.965 51.1914C284.965 55.4089 290.549 55.3495 290.549 55.3495L290.49 60.161C290.49 60.161 284.667 60.3392 281.578 56.0029C280.093 53.9832 279.439 51.726 279.439 48.7559C279.439 35.3905 279.439 14.897 279.439 14.897Z" fill="black"/>
|
||||||
|
<path d="M261.065 43.7068C256.728 43.7068 253.757 44.182 252.094 45.1918C250.431 46.2017 249.599 47.8649 249.599 50.241C249.599 54.0427 252.272 56.4187 256.55 56.4187C262.491 56.4187 266.352 51.8448 266.352 44.8948V43.7068H261.065ZM271.64 51.9636C271.64 56.1811 275.62 55.8247 275.62 55.8247V59.9234C275.62 59.9234 272.59 60.2799 270.511 58.6166C268.61 57.1316 267.481 54.9337 267.481 54.9337C265.045 58.9136 260.411 60.8739 255.124 60.8739C248.47 60.8739 244.252 56.8346 244.252 50.5974C244.252 43.291 249.183 39.5487 258.867 39.5487H266.352V39.0141C266.352 34.0837 263.56 31.2919 257.678 31.2919C254.946 31.2919 251.44 33.3709 251.559 36.935L246.628 37.0538C246.747 28.6782 254.292 26.7773 257.797 26.7773C266.946 26.7773 271.699 31.5889 271.699 41.5089C271.699 41.5089 271.58 47.8055 271.64 51.9636Z" fill="black"/>
|
||||||
|
<path d="M233.216 32.4799V14.897H238.563C238.563 14.897 238.682 38.42 238.682 50.8943C238.682 55.2901 238.682 59.864 238.682 59.864L233.81 59.9234L233.87 55.4089C231.671 59.27 227.572 60.8738 222.819 60.8738C218.898 60.8738 215.69 59.3294 213.195 56.1811C210.759 53.0328 209.512 48.9341 209.512 43.8256C209.512 38.717 210.759 34.6183 213.195 31.47C215.69 28.3217 218.898 26.7773 222.819 26.7773C227.572 26.7773 231.018 28.6187 233.216 32.4799ZM215.037 43.8256C215.037 51.6666 218.423 56.4187 224.126 56.4187C229.77 56.4187 233.216 51.6666 233.216 43.8256C233.216 35.9845 229.77 31.2324 224.126 31.2324C218.423 31.2324 215.037 35.9845 215.037 43.8256Z" fill="black"/>
|
||||||
|
<path d="M204.591 50.3004C204.71 55.7653 204.769 59.864 204.769 59.864L199.303 59.9234C199.303 59.9234 199.422 60.7551 199.244 51.0726C199.125 45.4294 199.125 40.6179 199.125 40.6179C199.125 34.4401 196.689 31.4107 191.937 31.4107C186.174 31.4107 182.609 35.3312 182.609 41.6871V60.0422H177.262C177.262 60.0422 177.262 46.7363 177.262 37.1726C177.262 32.5987 173.936 33.0145 173.936 33.0145V27.5496C173.936 27.5496 177.441 27.5496 179.401 29.688C181.124 31.5889 181.183 33.7273 181.183 33.7273C182.372 29.7474 187.362 26.7773 192.59 26.7773C200.016 26.7773 204.472 31.4701 204.472 40.4397C204.472 40.4397 204.472 41.5089 204.591 50.3004Z" fill="black"/>
|
||||||
|
<path d="M159.067 43.7068C154.73 43.7068 151.759 44.182 150.096 45.1918C148.432 46.2017 147.601 47.8649 147.601 50.241C147.601 54.0427 150.274 56.4187 154.552 56.4187C160.493 56.4187 164.354 51.8448 164.354 44.8948V43.7068H159.067ZM169.642 51.9636C169.642 56.1811 173.622 55.8247 173.622 55.8247V59.9234C173.622 59.9234 170.592 60.2799 168.513 58.6166C166.612 57.1316 165.483 54.9337 165.483 54.9337C163.047 58.9136 158.413 60.8739 153.126 60.8739C146.472 60.8739 142.254 56.8346 142.254 50.5974C142.254 43.291 147.185 39.5487 156.869 39.5487H164.354V39.0141C164.354 34.0837 161.562 31.2919 155.68 31.2919C152.948 31.2919 149.442 33.3709 149.561 36.935L144.63 37.0538C144.749 28.6782 152.294 26.7773 155.799 26.7773C164.948 26.7773 169.701 31.5889 169.701 41.5089C169.701 41.5089 169.582 47.8055 169.642 51.9636Z" fill="black"/>
|
||||||
|
<path d="M8.40549 86.8917C3.76365 86.8917 0 83.0908 0 78.4008V55.9119C0 51.2229 3.76265 47.421 8.40549 47.421H121.955C121.91 52.4182 121.875 57.4144 121.851 62.4106C121.821 68.5717 121.808 74.7338 121.81 80.8959C124.658 80.8726 127.506 80.8482 130.355 80.8249V86.8907L8.40549 86.8917Z" fill="#FF4F00"/>
|
||||||
|
<path d="M124.912 40.1348H9.02975C4.08481 40.1348 0.0752715 36.3339 0.0752715 31.6439V8.49088C0.0752715 3.80189 4.08481 0 9.02975 0H130.282C130.264 1.47615 130.246 2.95331 130.229 4.42945C129.018 4.43249 127.808 4.43554 126.596 4.43858C125.745 8.73218 124.895 13.0268 124.045 17.3204C122.613 23.2077 121.182 29.0951 119.749 34.9814H124.935C124.927 36.6989 124.919 38.4173 124.912 40.1348Z" fill="#FF4F00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.4 KiB |
53
Makefile
Executable file → Normal file
@@ -1,36 +1,39 @@
|
|||||||
extract_translations: ## no prerequisites so we can control order of operations
|
transifex_utils = ./node_modules/.bin/transifex-utils.js
|
||||||
echo "We have to define this target due to tooling assumptions"
|
i18n = ./src/i18n
|
||||||
echo "Also we have to npm install using this hook b/c there's no other place for it in the current setup"
|
transifex_input = $(i18n)/transifex_input.json
|
||||||
npm install
|
|
||||||
npm run-script i18n_extract
|
# This directory must match .babelrc .
|
||||||
|
transifex_temp = ./temp/babel-plugin-formatjs
|
||||||
|
|
||||||
|
build:
|
||||||
|
rm -rf ./dist
|
||||||
|
./node_modules/.bin/fedx-scripts babel src --out-dir dist --source-maps --ignore **/*.test.jsx,**/__mocks__,**/__snapshots__,**/setupTest.js --copy-files
|
||||||
|
@# --copy-files will bring in everything else that wasn't processed by babel. Remove what we don't want.
|
||||||
|
@rm -rf dist/**/*.test.jsx
|
||||||
|
@rm -rf dist/**/__snapshots__
|
||||||
|
@rm -rf dist/__mocks__
|
||||||
|
|
||||||
|
requirements:
|
||||||
|
npm ci
|
||||||
|
|
||||||
i18n.extract:
|
i18n.extract:
|
||||||
# Pulling display strings from .jsx files into .json files...
|
# Pulling display strings from .jsx files into .json files...
|
||||||
|
rm -rf $(transifex_temp)
|
||||||
npm run-script i18n_extract
|
npm run-script i18n_extract
|
||||||
|
|
||||||
i18n.concat:
|
i18n.concat:
|
||||||
# Gathering JSON messages into one file...
|
# Gathering JSON messages into one file...
|
||||||
./src/i18n/i18n-concat.js ./temp/src ./src/i18n/transifex_input.json
|
$(transifex_utils) $(transifex_temp) $(transifex_input)
|
||||||
|
|
||||||
i18n.pre_validate: | i18n.extract i18n.concat
|
extract_translations: | requirements i18n.extract i18n.concat
|
||||||
git diff --exit-code ./src/i18n/transifex_input.json
|
|
||||||
|
|
||||||
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/frontend-component-footer/translation/en/strings/
|
# Despite the name, we actually need this target to detect changes in the incoming translated message files as well.
|
||||||
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/frontend-component-footer/source/
|
detect_changed_source_translations:
|
||||||
|
# Checking for changed translations...
|
||||||
|
git diff --exit-code $(i18n)
|
||||||
|
|
||||||
# push translations to Transifex, doing magic so we can include the translator comments
|
|
||||||
push_translations: | i18n.extract
|
|
||||||
# Adding translator comments...
|
|
||||||
# Fetching strings from Transifex...
|
|
||||||
./node_modules/reactifex/bash_scripts/get_hashed_strings.sh $(tx_url1)
|
|
||||||
# Writing out comments to file...
|
|
||||||
./src/i18n/i18n-concat.js ./temp/src --comments
|
|
||||||
# Adding comments to Transifex...
|
|
||||||
./node_modules/reactifex/bash_scripts/put_comments.sh $(tx_url2)
|
|
||||||
|
|
||||||
# pull translations from Transifex
|
|
||||||
pull_translations: ## must be exactly this name for edx tooling support, see ecommerce-scripts/transifex/pull.py
|
|
||||||
tx pull -f --mode reviewed --language="ar,fr,es_419,zh_CN"
|
|
||||||
|
|
||||||
|
# This target is used by Travis.
|
||||||
validate-no-uncommitted-package-lock-changes:
|
validate-no-uncommitted-package-lock-changes:
|
||||||
|
# Checking for package-lock.json changes...
|
||||||
git diff --exit-code package-lock.json
|
git diff --exit-code package-lock.json
|
||||||
|
|||||||
205
README.rst
@@ -1,45 +1,202 @@
|
|||||||
|
#########################
|
||||||
frontend-component-footer
|
frontend-component-footer
|
||||||
=========================
|
#########################
|
||||||
|
|
||||||
|Build Status| |Coveralls| |npm_version| |npm_downloads| |license|
|
|Build Status| |Codecov| |npm_version| |npm_downloads| |license| |semantic-release|
|
||||||
|semantic-release|
|
|
||||||
|
|
||||||
frontend-component-footer is a library containing a site footer
|
********
|
||||||
component for use when building edX frontend applications.
|
Purpose
|
||||||
|
********
|
||||||
|
|
||||||
At this time, this component is hard-coded to match the legacy LMS site footer, including all of its links. As implemented, this component should probably be called the ``frontend-component-lms-footer``.
|
A generic footer for Open edX micro-frontend applications. It includes a logo and an optional language selector dropdown.
|
||||||
|
|
||||||
Usage
|
***************
|
||||||
-----
|
Getting Started
|
||||||
|
***************
|
||||||
|
|
||||||
To install frontend-component-footer into your project::
|
Prerequisites
|
||||||
|
=============
|
||||||
|
|
||||||
npm i --save @edx/frontend-component-footer
|
The `devstack`_ is currently recommended as a development environment for your
|
||||||
|
new MFE. If you start it with ``make dev.up.lms`` that should give you
|
||||||
|
everything you need as a companion to this frontend.
|
||||||
|
|
||||||
The component expects properties specifying the various URLs that are
|
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
|
||||||
linked in the footer. See the sample app in `src/index.jsx <src/index.jsx>`__ for an example
|
to the `relevant tutor-mfe documentation`_ to get started using it.
|
||||||
of how the SiteFooter component can be specified.
|
|
||||||
|
.. _Devstack: https://github.com/openedx/devstack
|
||||||
|
|
||||||
|
.. _Tutor: https://github.com/overhangio/tutor
|
||||||
|
|
||||||
|
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
============
|
||||||
|
|
||||||
|
This component uses ``@edx/frontend-platform`` services such as i18n, analytics, configuration, and the ``AppContext`` React component, and expects that it has been loaded into a micro-frontend that has been properly initialized via ``@edx/frontend-platform``'s ``initialize`` function. `Please visit the frontend template application to see an example. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx>`_
|
||||||
|
|
||||||
|
Environment Variables
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This component requires that the following environment variable be set by the consuming micro-frontend.
|
||||||
|
|
||||||
|
* ``LMS_BASE_URL`` - The URL of the LMS of your Open edX instance.
|
||||||
|
* ``LOGO_TRADEMARK_URL`` - This is a URL to a logo for use in the footer. This is a different environment variable than ``LOGO_URL`` (used in frontend-component-header) to accommodate sites that would like to have additional trademark information on a logo in the footer, such as a (tm) or (r) symbol.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
To install this footer into your Open edX micro-frontend, run the following command in your MFE:
|
||||||
|
|
||||||
|
``npm i --save @edx/frontend-component-footer``
|
||||||
|
|
||||||
|
This will make the component available to be imported into your application.
|
||||||
|
|
||||||
|
Cloning and Startup
|
||||||
|
===================
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
|
||||||
|
1. Clone your new repo:
|
||||||
|
|
||||||
|
``git clone https://github.com/openedx/frontend-component-footer.git``
|
||||||
|
|
||||||
|
2. Use node v24.x.
|
||||||
|
|
||||||
|
The current version of the micro-frontend build scripts support node 24.
|
||||||
|
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-component-footer && npm ci``
|
||||||
|
|
||||||
|
4. Start the dev server:
|
||||||
|
|
||||||
|
``npm start``
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
This library has the following exports:
|
||||||
|
|
||||||
|
* ``(default)``: The footer as a React component.
|
||||||
|
* ``messages``: Internationalization messages suitable for use with `@edx/frontend-platform/i18n <https://edx.github.io/frontend-platform/module-Internationalization.html>`_
|
||||||
|
* ``dist/footer.scss``: A SASS file which contains style information for the component. It should be imported into the micro-frontend's own SCSS file.
|
||||||
|
|
||||||
|
<Footer /> component props
|
||||||
|
==========================
|
||||||
|
|
||||||
|
* onLanguageSelected: Provides the footer with an event handler for when the user selects a
|
||||||
|
language from its dropdown.
|
||||||
|
* supportedLanguages: An array of objects representing available languages. See example below for object shape.
|
||||||
|
|
||||||
|
Plugin
|
||||||
|
======
|
||||||
|
The footer can be replaced or modified using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
|
||||||
|
|
||||||
|
Information on how to replace or modify the footer is available `here </src/plugin-slots>`_.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
Component Usage Example::
|
||||||
|
|
||||||
|
import Footer, { messages } from '@edx/frontend-component-footer';
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
<Footer
|
||||||
|
onLanguageSelected={(languageCode) => {/* set language */}}
|
||||||
|
supportedLanguages={[
|
||||||
|
{ label: 'English', value: 'en'},
|
||||||
|
{ label: 'Español', value: 'es' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
* `An example of minimal component and messages usage. <https://github.com/openedx/frontend-template-application/blob/3355bb3a96232390e9056f35b06ffa8f105ed7ca/src/index.jsx#L23>`_
|
||||||
|
* `An example of SCSS file usage. <https://github.com/openedx/frontend-template-application/blob/3cd5485bf387b8c479baf6b02bf59e3061dc3465/src/index.scss#L9>`_
|
||||||
|
|
||||||
|
|
||||||
This component uses ``react-intl``. Any containing app must provide ``react-intl`` as a peer dependency, and be wrapped inside an ``IntlProvider`` element, whether or not your consuming application is actually localized. For a basic default locale (English) version, follow the ``IntlProvider`` example in the sample application in `src/index.jsx <src/index.jsx>`__.
|
|
||||||
|
|
||||||
Development
|
Development
|
||||||
-----------
|
===========
|
||||||
|
|
||||||
Start the dev server::
|
Install dependencies::
|
||||||
|
|
||||||
npm i && npm start
|
npm i
|
||||||
|
|
||||||
Build the component::
|
Start the development server::
|
||||||
|
|
||||||
npm run build
|
npm start
|
||||||
|
|
||||||
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-component-footer.svg?branch=master
|
Build a production distribution::
|
||||||
:target: https://travis-ci.org/edx/frontend-component-footer
|
|
||||||
.. |Coveralls| image:: https://img.shields.io/coveralls/edx/frontend-component-footer.svg?branch=master
|
npm run build
|
||||||
:target: https://coveralls.io/github/edx/frontend-component-footer
|
|
||||||
|
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-component-footer/issues
|
||||||
|
|
||||||
|
For more information about these options, see the `Getting Help`_ page.
|
||||||
|
|
||||||
|
.. _Slack invitation: https://openedx.org/slack
|
||||||
|
.. _community Slack workspace: https://openedx.slack.com/
|
||||||
|
.. _#wg-frontend channel: https://openedx.slack.com/archives/C04BM6YC7A6
|
||||||
|
.. _Getting Help: https://openedx.org/community/connect
|
||||||
|
|
||||||
|
The Open edX Code of Conduct
|
||||||
|
============================
|
||||||
|
|
||||||
|
All community members are expected to follow the `Open edX Code of Conduct`_.
|
||||||
|
|
||||||
|
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
|
||||||
|
|
||||||
|
Reporting Security Issues
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Please do not report security issues in public. Please email security@openedx.org.
|
||||||
|
|
||||||
|
.. |Build Status| image:: https://api.travis-ci.com/edx/frontend-component-footer.svg?branch=master
|
||||||
|
:target: https://travis-ci.com/edx/frontend-component-footer
|
||||||
|
.. |Codecov| image:: https://img.shields.io/codecov/c/github/edx/frontend-component-footer
|
||||||
|
:target: @edx/frontend-component-footer
|
||||||
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-component-footer.svg
|
.. |npm_version| image:: https://img.shields.io/npm/v/@edx/frontend-component-footer.svg
|
||||||
:target: @edx/frontend-component-footer
|
:target: @edx/frontend-component-footer
|
||||||
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-component-footer.svg
|
.. |npm_downloads| image:: https://img.shields.io/npm/dt/@edx/frontend-component-footer.svg
|
||||||
|
|||||||
4
Standalone Logogram.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="81" height="54" viewBox="0 0 81 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.2088 53.8455C2.3323 53.8455 0 51.4901 0 48.5838V34.6478C0 31.7421 2.33168 29.3861 5.2088 29.3861H75.5746C75.5466 32.4828 75.5248 35.5788 75.5099 38.6749C75.4912 42.4928 75.4831 46.3114 75.4844 50.1299C77.2495 50.1155 79.0145 50.1004 80.7796 50.086V53.8448L5.2088 53.8455Z" fill="#FF4F00"/>
|
||||||
|
<path d="M77.4068 24.8709H5.59565C2.53132 24.8709 0.046645 22.5156 0.046645 19.6092V5.26167C0.046645 2.35597 2.53132 0 5.59565 0H80.7342C80.7237 0.914745 80.7125 1.83012 80.7019 2.74486C79.9512 2.74675 79.2011 2.74864 78.4504 2.75052C77.923 5.4112 77.3962 8.0725 76.8695 10.7332C75.9819 14.3815 75.095 18.0298 74.2075 21.6775H77.4211C77.4161 22.7417 77.4112 23.8066 77.4068 24.8709Z" fill="#FF4F00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 806 B |
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
|
module.exports = createConfig('babel-preserve-modules');
|
||||||
14
catalog-info.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 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-component-footer"
|
||||||
|
description: "A generic footer for the Open edX micro-frontend applications."
|
||||||
|
annotations:
|
||||||
|
openedx.org/arch-interest-groups: ""
|
||||||
|
spec:
|
||||||
|
owner: group:committers-frontend
|
||||||
|
type: "library"
|
||||||
|
lifecycle: "production"
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: ['@commitlint/config-angular'],
|
|
||||||
};
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// This is the common Webpack config. The dev and prod Webpack configs both
|
|
||||||
// inherit config defined here.
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: {
|
|
||||||
app: path.resolve(__dirname, '../src/index.jsx'),
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, '../dist'),
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.jsx'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
// This is the dev Webpack config. All settings here should prefer a fast build
|
|
||||||
// time at the expense of creating larger, unoptimized bundles.
|
|
||||||
const Merge = require('webpack-merge');
|
|
||||||
const path = require('path');
|
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
const commonConfig = require('./webpack.common.config.js');
|
|
||||||
|
|
||||||
module.exports = Merge.smart(commonConfig, {
|
|
||||||
mode: 'development',
|
|
||||||
entry: [
|
|
||||||
// enable react's custom hot dev client so we get errors reported in the browser
|
|
||||||
require.resolve('react-dev-utils/webpackHotDevClient'),
|
|
||||||
path.resolve(__dirname, '../src/index.jsx'),
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
// Specify file-by-file rules to Webpack. Some file-types need a particular kind of loader.
|
|
||||||
rules: [
|
|
||||||
// The babel-loader transforms newer ES2015+ syntax to older ES5 for older browsers.
|
|
||||||
// Babel is configured with the .babelrc file at the root of the project.
|
|
||||||
{
|
|
||||||
test: /\.(js|jsx)$/,
|
|
||||||
include: [
|
|
||||||
path.resolve(__dirname, '../src'),
|
|
||||||
],
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
// Caches result of loader to the filesystem. Future builds will attempt to read from the
|
|
||||||
// cache to avoid needing to run the expensive recompilation process on each run.
|
|
||||||
cacheDirectory: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// We are not extracting CSS from the javascript bundles in development because extracting
|
|
||||||
// prevents hot-reloading from working, it increases build time, and we don't care about
|
|
||||||
// flash-of-unstyled-content issues in development.
|
|
||||||
{
|
|
||||||
test: /(.scss|.css)$/,
|
|
||||||
use: [
|
|
||||||
'style-loader', // creates style nodes from JS strings
|
|
||||||
{
|
|
||||||
loader: 'css-loader', // translates CSS into CommonJS
|
|
||||||
options: {
|
|
||||||
sourceMap: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'sass-loader', // compiles Sass to CSS
|
|
||||||
options: {
|
|
||||||
sourceMap: true,
|
|
||||||
includePaths: [
|
|
||||||
path.join(__dirname, '../node_modules'),
|
|
||||||
path.join(__dirname, '../src'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// Webpack, by default, uses the url-loader for images and fonts that are required/included by
|
|
||||||
// files it processes, which just base64 encodes them and inlines them in the javascript
|
|
||||||
// bundles. This makes the javascript bundles ginormous and defeats caching so we will use the
|
|
||||||
// file-loader instead to copy the files directly to the output directory.
|
|
||||||
{
|
|
||||||
test: /\.(woff2?|ttf|svg|eot)(\?v=\d+\.\d+\.\d+)?$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(jpe?g|png|gif|ico)(\?v=\d+\.\d+\.\d+)?$/,
|
|
||||||
use: [
|
|
||||||
'file-loader',
|
|
||||||
{
|
|
||||||
loader: 'image-webpack-loader',
|
|
||||||
options: {
|
|
||||||
optimizationlevel: 7,
|
|
||||||
mozjpeg: {
|
|
||||||
progressive: true,
|
|
||||||
},
|
|
||||||
gifsicle: {
|
|
||||||
interlaced: false,
|
|
||||||
},
|
|
||||||
pngquant: {
|
|
||||||
quality: '65-90',
|
|
||||||
speed: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// Specify additional processing or side-effects done on the Webpack output bundles as a whole.
|
|
||||||
plugins: [
|
|
||||||
// Generates an HTML file in the output directory.
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
inject: true, // Appends script tags linking to the webpack bundles at the end of the body
|
|
||||||
template: path.resolve(__dirname, '../public/index.html'),
|
|
||||||
}),
|
|
||||||
new webpack.EnvironmentPlugin({
|
|
||||||
NODE_ENV: 'development',
|
|
||||||
}),
|
|
||||||
// when the --hot option is not passed in as part of the command
|
|
||||||
// the HotModuleReplacementPlugin has to be specified in the Webpack configuration
|
|
||||||
// https://webpack.js.org/configuration/dev-server/#devserver-hot
|
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
],
|
|
||||||
// This configures webpack-dev-server which serves bundles from memory and provides live
|
|
||||||
// reloading.
|
|
||||||
devServer: {
|
|
||||||
host: '0.0.0.0',
|
|
||||||
port: 3000,
|
|
||||||
historyApiFallback: true,
|
|
||||||
hot: true,
|
|
||||||
inline: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// This is the prod Webpack config. All settings here should prefer smaller,
|
|
||||||
// optimized bundles at the expense of a longer build time.
|
|
||||||
const Merge = require('webpack-merge');
|
|
||||||
const commonConfig = require('./webpack.common.config.js');
|
|
||||||
const path = require('path');
|
|
||||||
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
|
||||||
|
|
||||||
module.exports = Merge.smart(commonConfig, {
|
|
||||||
mode: 'production',
|
|
||||||
devtool: 'source-map',
|
|
||||||
entry: './src/lib/index.js',
|
|
||||||
output: {
|
|
||||||
path: path.resolve(__dirname, '../dist'),
|
|
||||||
library: 'frontend-component-footer',
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.js', '.jsx'],
|
|
||||||
alias: {
|
|
||||||
react: path.resolve(__dirname, './node_modules/react'),
|
|
||||||
'react-dom': path.resolve(__dirname, './node_modules/react-dom'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
react: {
|
|
||||||
commonjs: 'react',
|
|
||||||
commonjs2: 'react',
|
|
||||||
amd: 'React',
|
|
||||||
root: 'React',
|
|
||||||
},
|
|
||||||
'react-dom': {
|
|
||||||
commonjs: 'react-dom',
|
|
||||||
commonjs2: 'react-dom',
|
|
||||||
amd: 'ReactDOM',
|
|
||||||
root: 'ReactDOM',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
// Cleans the dist directory before each build
|
|
||||||
new CleanWebpackPlugin(['dist'], {
|
|
||||||
root: path.join(__dirname, '../'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(js|jsx)$/,
|
|
||||||
include: [
|
|
||||||
path.resolve(__dirname, '../src/lib'),
|
|
||||||
],
|
|
||||||
exclude: /(node_modules)/,
|
|
||||||
use: [
|
|
||||||
{ loader: 'babel-loader' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
33
example/index.jsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import 'babel-polyfill';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { initialize, getConfig, subscribe, APP_READY } from '@edx/frontend-platform';
|
||||||
|
import { AppContext, AppProvider } from '@edx/frontend-platform/react';
|
||||||
|
import Footer from '@edx/frontend-component-footer';
|
||||||
|
|
||||||
|
import './index.scss';
|
||||||
|
|
||||||
|
subscribe(APP_READY, () => {
|
||||||
|
ReactDOM.render(
|
||||||
|
<AppProvider>
|
||||||
|
<AppContext.Provider value={{
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: getConfig(),
|
||||||
|
}}>
|
||||||
|
<Footer
|
||||||
|
onLanguageSelected={() => {}}
|
||||||
|
supportedLanguages={[
|
||||||
|
{ label: 'English', value: 'en' },
|
||||||
|
{ label: 'Español', value: 'es' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</AppContext.Provider>
|
||||||
|
</AppProvider>,
|
||||||
|
document.getElementById('root'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
initialize({
|
||||||
|
messages: []
|
||||||
|
});
|
||||||
4
example/index.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@use "@openedx/paragon/dist/core.min.css" as paragonCore;
|
||||||
|
@use "@openedx/paragon/dist/light.min.css" as paragonLight;
|
||||||
|
|
||||||
|
@import "@edx/frontend-component-footer/footer";
|
||||||
7
jest.config.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
|
module.exports = createConfig('jest', {
|
||||||
|
setupFiles: [
|
||||||
|
'<rootDir>/src/setupTest.js',
|
||||||
|
],
|
||||||
|
});
|
||||||
59151
package-lock.json
generated
138
package.json
@@ -1,110 +1,70 @@
|
|||||||
{
|
{
|
||||||
"name": "@edx/frontend-component-footer",
|
"name": "@edx/frontend-component-footer",
|
||||||
"version": "1.0.0-semantically-released",
|
"version": "1.0.0-semantically-released",
|
||||||
"description": "Site footer component for use when building edX frontend applications",
|
"description": "Footer component for use when building Open edX frontend applications",
|
||||||
"main": "dist/main.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/main.js",
|
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "NODE_ENV=production BABEL_ENV=production webpack --config=config/webpack.prod.config.js",
|
"build": "make build",
|
||||||
"gc": "commit",
|
"i18n_extract": "fedx-scripts formatjs extract",
|
||||||
"commitmsg": "commitlint -e $GIT_PARAMS",
|
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
|
||||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
|
||||||
"i18n_extract": "BABEL_ENV=i18n babel src --quiet > /dev/null",
|
"snapshot": "fedx-scripts jest --updateSnapshot",
|
||||||
"lint": "eslint --ext .js --ext .jsx .",
|
"start": "fedx-scripts webpack-dev-server --progress",
|
||||||
"precommit": "npm run lint",
|
"start:with-theme": "paragon install-theme && npm start && npm install",
|
||||||
"prepublishOnly": "npm run build",
|
"test": "fedx-scripts jest --coverage",
|
||||||
"semantic-release": "semantic-release",
|
"test:watch": "npm run test -- --watch"
|
||||||
"start": "NODE_ENV=development BABEL_ENV=development node_modules/.bin/webpack-dev-server --config=config/webpack.dev.config.js --progress",
|
|
||||||
"test": "jest --coverage",
|
|
||||||
"snapshot": "jest --updateSnapshot",
|
|
||||||
"travis-deploy-once": "travis-deploy-once",
|
|
||||||
"watch": "watch 'npm run build' ./src"
|
|
||||||
},
|
},
|
||||||
|
"files": [
|
||||||
|
"/dist"
|
||||||
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/edx/frontend-component-footer.git"
|
"url": "git+https://github.com/openedx/frontend-component-footer.git"
|
||||||
},
|
},
|
||||||
"author": "edX",
|
"author": "edX",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/edx/frontend-component-footer/issues"
|
"url": "https://github.com/openedx/frontend-component-footer/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/edx/frontend-component-footer#readme",
|
"homepage": "https://github.com/openedx/frontend-component-footer#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^7.1.2",
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
"@commitlint/config-angular": "^6.0.2",
|
"@edx/browserslist-config": "^1.1.1",
|
||||||
"@commitlint/prompt": "^6.0.2",
|
"@edx/frontend-platform": "^8.2.1",
|
||||||
"@commitlint/prompt-cli": "^6.0.2",
|
"@openedx/frontend-build": "^14.3.1",
|
||||||
"@edx/paragon": "^3.8.0",
|
"@openedx/paragon": "^23.3.0",
|
||||||
"babel-cli": "^6.26.0",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"babel-core": "^6.26.3",
|
"@testing-library/react": "^16.2.0",
|
||||||
"babel-loader": "^7.1.5",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"babel-plugin-react-intl": "^3.0.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"babel-plugin-rewire": "^1.2.0",
|
"prop-types": "15.8.1",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
"react": "^18.3.1",
|
||||||
"babel-preset-env": "^1.7.0",
|
"react-dom": "^18.3.1",
|
||||||
"babel-preset-react": "^6.24.1",
|
"react-redux": "8.1.3",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"react-router-dom": "6.30.2",
|
||||||
"coveralls": "^3.0.0",
|
"react-test-renderer": "18.3.1",
|
||||||
"css-loader": "^0.28.9",
|
"redux": "4.2.1",
|
||||||
"enzyme": "^3.3.0",
|
"semantic-release": "21.1.2",
|
||||||
"enzyme-adapter-react-16": "^1.1.1",
|
"ts-jest": "^29.1.2"
|
||||||
"eslint": "^5.2.0",
|
},
|
||||||
"eslint-config-edx": "^4.0.4",
|
"dependencies": {
|
||||||
"eslint-plugin-jsx-a11y": "^6.1.2",
|
"@fortawesome/fontawesome-svg-core": "6.7.2",
|
||||||
"file-loader": "^1.1.9",
|
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
||||||
"glob": "^7.1.3",
|
"@fortawesome/free-regular-svg-icons": "6.7.2",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||||
"husky": "^0.14.3",
|
"@fortawesome/react-fontawesome": "0.2.6",
|
||||||
"image-webpack-loader": "^4.2.0",
|
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||||
"jest": "23.6.0",
|
"classnames": "^2.5.1",
|
||||||
"node-sass": "^4.7.2",
|
"lodash": "^4.17.21"
|
||||||
"prop-types": "^15.5.10",
|
|
||||||
"react": "^16.4.2",
|
|
||||||
"react-dev-utils": "^5.0.0",
|
|
||||||
"react-dom": "^16.2.0",
|
|
||||||
"react-intl": "^2.8.0",
|
|
||||||
"react-test-renderer": "^16.6.0",
|
|
||||||
"reactifex": "^1.1.1",
|
|
||||||
"sass-loader": "^6.0.6",
|
|
||||||
"semantic-release": "^15.1.7",
|
|
||||||
"source-map-loader": "^0.2.1",
|
|
||||||
"style-loader": "^0.20.2",
|
|
||||||
"travis-deploy-once": "^5.0.0",
|
|
||||||
"watch": "^1.0.2",
|
|
||||||
"webpack": "^4.19.1",
|
|
||||||
"webpack-cli": "^3.1.0",
|
|
||||||
"webpack-dev-server": "^3.1.9",
|
|
||||||
"webpack-merge": "^4.2.1"
|
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@edx/paragon": "^3.8.0",
|
"@edx/frontend-platform": "^7.0.0 || ^8.0.0",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"@openedx/paragon": ">= 21.11.3 < 24.0.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react": "^16.4.2",
|
"react": "^16.9.0 || ^17.0.0 || ^18.0.0",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0"
|
||||||
"react-intl": "2.x",
|
|
||||||
"webpack": "^4.19.1",
|
|
||||||
"webpack-merge": "^4.2.1"
|
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"setupFiles": [
|
|
||||||
"./src/tests/setupTest.js"
|
|
||||||
],
|
|
||||||
"collectCoverageFrom": [
|
|
||||||
"src/lib/**/*.{js,jsx}",
|
|
||||||
"!src/tests/setupTest.js",
|
|
||||||
"!src/index.js",
|
|
||||||
"!**/node_modules/**",
|
|
||||||
"!**/tests/**"
|
|
||||||
],
|
|
||||||
"moduleNameMapper": {
|
|
||||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
|
|
||||||
"\\.(css|scss)$": "identity-obj-proxy"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en-us">
|
<html lang="en-us" dir="ltr">
|
||||||
<head>
|
<head>
|
||||||
<title>Footer | edX</title>
|
<title>Footer</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">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
33
renovate.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"config:base",
|
||||||
|
"schedule:weekly",
|
||||||
|
":automergeLinters",
|
||||||
|
":automergeMinor",
|
||||||
|
":automergeTesters",
|
||||||
|
":enableVulnerabilityAlerts",
|
||||||
|
":rebaseStalePrs",
|
||||||
|
":semanticCommits",
|
||||||
|
":updateNotScheduled"
|
||||||
|
],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchDepTypes": [
|
||||||
|
"devDependencies"
|
||||||
|
],
|
||||||
|
"matchUpdateTypes": [
|
||||||
|
"lockFileMaintenance",
|
||||||
|
"minor",
|
||||||
|
"patch",
|
||||||
|
"pin"
|
||||||
|
],
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchPackagePatterns": ["@edx", "@openedx"],
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timezone": "America/New_York"
|
||||||
|
}
|
||||||
50
src/_footer.scss
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
$gray-footer: #fcfcfc !default;
|
||||||
|
|
||||||
|
// Andal Learning Brand Colors - Override Paragon variables
|
||||||
|
:root {
|
||||||
|
--pgn-color-primary: #ff4f00;
|
||||||
|
--pgn-color-primary-100: #ffe6cc;
|
||||||
|
--pgn-color-primary-200: #ffcc99;
|
||||||
|
--pgn-color-primary-300: #ffb366;
|
||||||
|
--pgn-color-primary-400: #ff9933;
|
||||||
|
--pgn-color-primary-500: #ff4f00;
|
||||||
|
--pgn-color-primary-600: #cc3f00;
|
||||||
|
--pgn-color-primary-700: #992f00;
|
||||||
|
--pgn-color-primary-800: #661f00;
|
||||||
|
--pgn-color-primary-900: #330f00;
|
||||||
|
--pgn-color-action-primary: #ff4f00;
|
||||||
|
--pgn-color-action-primary-hover: #cc3f00;
|
||||||
|
--pgn-color-action-primary-focus: #992f00;
|
||||||
|
--pgn-color-action-primary-active: #992f00;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override all button variants to use Andal orange
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #ff4f00 !important;
|
||||||
|
border-color: #ff4f00 !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #cc3f00 !important;
|
||||||
|
border-color: #cc3f00 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
color: #ff4f00 !important;
|
||||||
|
border-color: #ff4f00 !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #ff4f00 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-brand {
|
||||||
|
background-color: #ff4f00 !important;
|
||||||
|
border-color: #ff4f00 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
background-color: var(--pgn-color-light-100, $gray-footer);
|
||||||
|
}
|
||||||
17
src/assets/complete-logo.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<svg width="518" height="87" viewBox="0 0 518 87" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M512.654 43.4098C512.654 35.747 509.327 31.2325 503.564 31.2325C497.861 31.2325 494.474 35.747 494.474 43.4098C494.474 51.0726 497.861 55.5871 503.564 55.5871C509.327 55.5871 512.654 51.0726 512.654 43.4098ZM518 56.0029C518 67.111 513.188 72.3978 503.029 72.3978C499.286 72.3978 496.019 71.8632 492.87 70.6751V65.5072C496.019 67.1704 499.049 68.0021 502.198 68.0021C509.148 68.0021 512.654 64.3786 512.654 56.9534V54.3397C510.455 58.1414 507.01 60.0422 502.257 60.0422C498.277 60.0422 495.009 58.5572 492.573 55.5277C490.137 52.4982 488.949 48.4589 488.949 43.4098C488.949 38.3607 490.137 34.3807 492.573 31.3513C495.009 28.3218 498.277 26.7773 502.257 26.7773C507.01 26.7773 510.455 28.6782 512.654 32.4799V27.5496H518V56.0029Z" fill="black"/>
|
||||||
|
<path d="M484.03 50.3004C484.149 55.7653 484.209 59.864 484.209 59.864L478.743 59.9234C478.743 59.9234 478.862 60.7551 478.683 51.0726C478.565 45.4294 478.565 40.6179 478.565 40.6179C478.565 34.4401 476.129 31.4107 471.376 31.4107C465.613 31.4107 462.049 35.3312 462.049 41.6871V60.0422H456.702C456.702 60.0422 456.702 46.7363 456.702 37.1726C456.702 32.5987 453.375 33.0145 453.375 33.0145V27.5496C453.375 27.5496 456.88 27.5496 458.841 29.688C460.564 31.5889 460.623 33.7273 460.623 33.7273C461.811 29.7474 466.802 26.7773 472.03 26.7773C479.456 26.7773 483.911 31.4701 483.911 40.4397C483.911 40.4397 483.911 41.5089 484.03 50.3004Z" fill="black"/>
|
||||||
|
<path d="M443.611 27.5485H448.958C448.958 27.5485 448.661 42.8147 448.779 51.4873C448.779 55.5861 452.166 55.1702 452.166 55.1702V60.2194C452.166 60.2194 443.492 60.0412 443.611 52.2002C443.73 42.6365 443.611 27.5485 443.611 27.5485ZM446.225 14.3613C444.264 14.3613 442.66 15.9652 442.66 17.9254C442.66 19.8857 444.264 21.4895 446.225 21.4895C448.185 21.4895 449.789 19.9451 449.789 17.9254C449.789 15.9058 448.185 14.3613 446.225 14.3613Z" fill="black"/>
|
||||||
|
<path d="M436.376 50.3004C436.495 55.7653 436.554 59.864 436.554 59.864L431.089 59.9234C431.089 59.9234 431.207 60.7551 431.029 51.0726C430.91 45.4294 430.91 40.6179 430.91 40.6179C430.91 34.4401 428.475 31.4107 423.722 31.4107C417.959 31.4107 414.395 35.3312 414.395 41.6871V60.0422H409.048C409.048 60.0422 409.048 46.7363 409.048 37.1726C409.048 32.5987 405.721 33.0145 405.721 33.0145V27.5496C405.721 27.5496 409.226 27.5496 411.186 29.688C412.909 31.5889 412.969 33.7273 412.969 33.7273C414.157 29.7474 419.147 26.7773 424.375 26.7773C431.801 26.7773 436.257 31.4701 436.257 40.4397C436.257 40.4397 436.257 41.5089 436.376 50.3004Z" fill="black"/>
|
||||||
|
<path d="M403.931 32.4208C402.208 32.5396 400.961 32.5396 399 33.49C396.386 34.7969 393.237 37.7076 393.237 42.9349C393.237 49.8849 393.237 60.0426 393.237 60.0426H387.891V27.5499H393.237V32.599C395.257 29.5695 397.337 27.3717 401.911 27.1935C402.684 27.1341 402.446 27.1341 403.931 27.1935V32.4208Z" fill="black"/>
|
||||||
|
<path d="M369.696 43.7068C365.359 43.7068 362.388 44.182 360.725 45.1918C359.061 46.2017 358.23 47.8649 358.23 50.241C358.23 54.0427 360.903 56.4187 365.181 56.4187C371.122 56.4187 374.983 51.8448 374.983 44.8948V43.7068H369.696ZM380.271 51.9636C380.271 56.1811 384.251 55.8247 384.251 55.8247V59.9234C384.251 59.9234 381.221 60.2799 379.142 58.6166C377.241 57.1316 376.112 54.9337 376.112 54.9337C373.676 58.9136 369.042 60.8739 363.755 60.8739C357.101 60.8739 352.883 56.8346 352.883 50.5974C352.883 43.291 357.814 39.5487 367.498 39.5487H374.983V39.0141C374.983 34.0837 372.191 31.2919 366.309 31.2919C363.577 31.2919 360.071 33.3709 360.19 36.935L355.259 37.0538C355.378 28.6782 362.923 26.7773 366.428 26.7773C375.577 26.7773 380.33 31.5889 380.33 41.5089C380.33 41.5089 380.211 47.8055 380.271 51.9636Z" fill="black"/>
|
||||||
|
<path d="M349.423 42.4594V45.073H324.887C325.362 52.4388 329.402 56.3593 336.472 56.3593C340.571 56.3593 344.374 55.4089 348.176 53.3299V58.379C344.374 60.0422 340.274 60.8739 336.175 60.8739C331.006 60.8739 326.848 59.3888 323.818 56.3593C320.788 53.3299 319.303 49.2906 319.303 44.1226C319.303 38.8359 320.729 34.6184 323.58 31.4701C326.491 28.3218 330.353 26.7773 335.224 26.7773C339.621 26.7773 343.067 28.203 345.621 30.9949C348.176 33.7867 349.423 37.6478 349.423 42.4594ZM344.076 40.9149C344.017 35.0342 340.571 31.2919 335.284 31.2919C329.343 31.2919 325.6 34.7966 325.065 40.9149H344.076Z" fill="black"/>
|
||||||
|
<path d="M308.35 14.897H313.696C313.696 14.897 313.875 38.4794 313.875 51.1914C313.875 55.4089 319.459 55.3495 319.459 55.3495L319.4 60.161C319.4 60.161 313.578 60.3392 310.488 56.0029C309.003 53.9832 308.35 51.726 308.35 48.7559C308.35 35.3905 308.35 14.897 308.35 14.897Z" fill="black"/>
|
||||||
|
<path d="M279.439 14.897H284.786C284.786 14.897 284.965 38.4794 284.965 51.1914C284.965 55.4089 290.549 55.3495 290.549 55.3495L290.49 60.161C290.49 60.161 284.667 60.3392 281.578 56.0029C280.093 53.9832 279.439 51.726 279.439 48.7559C279.439 35.3905 279.439 14.897 279.439 14.897Z" fill="black"/>
|
||||||
|
<path d="M261.065 43.7068C256.728 43.7068 253.757 44.182 252.094 45.1918C250.431 46.2017 249.599 47.8649 249.599 50.241C249.599 54.0427 252.272 56.4187 256.55 56.4187C262.491 56.4187 266.352 51.8448 266.352 44.8948V43.7068H261.065ZM271.64 51.9636C271.64 56.1811 275.62 55.8247 275.62 55.8247V59.9234C275.62 59.9234 272.59 60.2799 270.511 58.6166C268.61 57.1316 267.481 54.9337 267.481 54.9337C265.045 58.9136 260.411 60.8739 255.124 60.8739C248.47 60.8739 244.252 56.8346 244.252 50.5974C244.252 43.291 249.183 39.5487 258.867 39.5487H266.352V39.0141C266.352 34.0837 263.56 31.2919 257.678 31.2919C254.946 31.2919 251.44 33.3709 251.559 36.935L246.628 37.0538C246.747 28.6782 254.292 26.7773 257.797 26.7773C266.946 26.7773 271.699 31.5889 271.699 41.5089C271.699 41.5089 271.58 47.8055 271.64 51.9636Z" fill="black"/>
|
||||||
|
<path d="M233.216 32.4799V14.897H238.563C238.563 14.897 238.682 38.42 238.682 50.8943C238.682 55.2901 238.682 59.864 238.682 59.864L233.81 59.9234L233.87 55.4089C231.671 59.27 227.572 60.8738 222.819 60.8738C218.898 60.8738 215.69 59.3294 213.195 56.1811C210.759 53.0328 209.512 48.9341 209.512 43.8256C209.512 38.717 210.759 34.6183 213.195 31.47C215.69 28.3217 218.898 26.7773 222.819 26.7773C227.572 26.7773 231.018 28.6187 233.216 32.4799ZM215.037 43.8256C215.037 51.6666 218.423 56.4187 224.126 56.4187C229.77 56.4187 233.216 51.6666 233.216 43.8256C233.216 35.9845 229.77 31.2324 224.126 31.2324C218.423 31.2324 215.037 35.9845 215.037 43.8256Z" fill="black"/>
|
||||||
|
<path d="M204.591 50.3004C204.71 55.7653 204.769 59.864 204.769 59.864L199.303 59.9234C199.303 59.9234 199.422 60.7551 199.244 51.0726C199.125 45.4294 199.125 40.6179 199.125 40.6179C199.125 34.4401 196.689 31.4107 191.937 31.4107C186.174 31.4107 182.609 35.3312 182.609 41.6871V60.0422H177.262C177.262 60.0422 177.262 46.7363 177.262 37.1726C177.262 32.5987 173.936 33.0145 173.936 33.0145V27.5496C173.936 27.5496 177.441 27.5496 179.401 29.688C181.124 31.5889 181.183 33.7273 181.183 33.7273C182.372 29.7474 187.362 26.7773 192.59 26.7773C200.016 26.7773 204.472 31.4701 204.472 40.4397C204.472 40.4397 204.472 41.5089 204.591 50.3004Z" fill="black"/>
|
||||||
|
<path d="M159.067 43.7068C154.73 43.7068 151.759 44.182 150.096 45.1918C148.432 46.2017 147.601 47.8649 147.601 50.241C147.601 54.0427 150.274 56.4187 154.552 56.4187C160.493 56.4187 164.354 51.8448 164.354 44.8948V43.7068H159.067ZM169.642 51.9636C169.642 56.1811 173.622 55.8247 173.622 55.8247V59.9234C173.622 59.9234 170.592 60.2799 168.513 58.6166C166.612 57.1316 165.483 54.9337 165.483 54.9337C163.047 58.9136 158.413 60.8739 153.126 60.8739C146.472 60.8739 142.254 56.8346 142.254 50.5974C142.254 43.291 147.185 39.5487 156.869 39.5487H164.354V39.0141C164.354 34.0837 161.562 31.2919 155.68 31.2919C152.948 31.2919 149.442 33.3709 149.561 36.935L144.63 37.0538C144.749 28.6782 152.294 26.7773 155.799 26.7773C164.948 26.7773 169.701 31.5889 169.701 41.5089C169.701 41.5089 169.582 47.8055 169.642 51.9636Z" fill="black"/>
|
||||||
|
<path d="M8.40549 86.8917C3.76365 86.8917 0 83.0908 0 78.4008V55.9119C0 51.2229 3.76265 47.421 8.40549 47.421H121.955C121.91 52.4182 121.875 57.4144 121.851 62.4106C121.821 68.5717 121.808 74.7338 121.81 80.8959C124.658 80.8726 127.506 80.8482 130.355 80.8249V86.8907L8.40549 86.8917Z" fill="#FF4F00"/>
|
||||||
|
<path d="M124.912 40.1348H9.02975C4.08481 40.1348 0.0752715 36.3339 0.0752715 31.6439V8.49088C0.0752715 3.80189 4.08481 0 9.02975 0H130.282C130.264 1.47615 130.246 2.95331 130.229 4.42945C129.018 4.43249 127.808 4.43554 126.596 4.43858C125.745 8.73218 124.895 13.0268 124.045 17.3204C122.613 23.2077 121.182 29.0951 119.749 34.9814H124.935C124.927 36.6989 124.919 38.4173 124.912 40.1348Z" fill="#FF4F00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.4 KiB |
87
src/components/Footer.jsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
|
import { ensureConfig, getConfig } from '@edx/frontend-platform';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import { Image, Hyperlink } from '@openedx/paragon';
|
||||||
|
|
||||||
|
import andalLogo from '../assets/complete-logo.svg';
|
||||||
|
import messages from './Footer.messages';
|
||||||
|
import LanguageSelector from './LanguageSelector';
|
||||||
|
|
||||||
|
ensureConfig([
|
||||||
|
'LMS_BASE_URL',
|
||||||
|
'LOGO_TRADEMARK_URL',
|
||||||
|
], 'Footer component');
|
||||||
|
|
||||||
|
const EVENT_NAMES = {
|
||||||
|
FOOTER_LINK: 'edx.bi.footer.link',
|
||||||
|
};
|
||||||
|
|
||||||
|
const SiteFooter = ({
|
||||||
|
supportedLanguages,
|
||||||
|
onLanguageSelected,
|
||||||
|
logo,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { config } = useContext(AppContext);
|
||||||
|
|
||||||
|
const showLanguageSelector = supportedLanguages.length > 0 && onLanguageSelected;
|
||||||
|
|
||||||
|
const externalLinkClickHandler = (event) => {
|
||||||
|
const label = event.currentTarget.getAttribute('href');
|
||||||
|
const eventName = EVENT_NAMES.FOOTER_LINK;
|
||||||
|
const properties = {
|
||||||
|
category: 'outbound_link',
|
||||||
|
label,
|
||||||
|
};
|
||||||
|
sendTrackEvent(eventName, properties);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
role="contentinfo"
|
||||||
|
className="footer d-flex border-top py-3 px-4"
|
||||||
|
>
|
||||||
|
<div className="container-fluid d-flex">
|
||||||
|
<Hyperlink
|
||||||
|
destination={getConfig().LMS_BASE_URL}
|
||||||
|
className="d-block"
|
||||||
|
onClick={externalLinkClickHandler}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
style={{ maxHeight: 45 }}
|
||||||
|
src={andalLogo}
|
||||||
|
alt={intl.formatMessage(messages['footer.logo.altText'])}
|
||||||
|
/>
|
||||||
|
</Hyperlink>
|
||||||
|
<div className="flex-grow-1" />
|
||||||
|
{showLanguageSelector && (
|
||||||
|
<LanguageSelector
|
||||||
|
options={supportedLanguages}
|
||||||
|
onSubmit={onLanguageSelected}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SiteFooter.propTypes = {
|
||||||
|
logo: PropTypes.string,
|
||||||
|
onLanguageSelected: PropTypes.func,
|
||||||
|
supportedLanguages: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
SiteFooter.defaultProps = {
|
||||||
|
logo: undefined,
|
||||||
|
onLanguageSelected: undefined,
|
||||||
|
supportedLanguages: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteFooter;
|
||||||
|
export { EVENT_NAMES };
|
||||||
156
src/components/Footer.messages.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
'footer.socialLinks.srText.facebook': {
|
||||||
|
id: 'footer.socialLinks.srText.facebook',
|
||||||
|
defaultMessage: 'Like edX on Facebook',
|
||||||
|
description: 'This is screenreader text for the edX Facebook social media link in the footer.',
|
||||||
|
},
|
||||||
|
'footer.socialLinks.srText.twitter': {
|
||||||
|
id: 'footer.socialLinks.srText.twitter',
|
||||||
|
defaultMessage: 'Follow edX on Twitter',
|
||||||
|
description: 'This is screenreader text for the edX Twitter social media link in the footer.',
|
||||||
|
},
|
||||||
|
'footer.socialLinks.srText.youtube': {
|
||||||
|
id: 'footer.socialLinks.srText.youtube',
|
||||||
|
defaultMessage: 'Subscribe to the edX YouTube channel',
|
||||||
|
description: 'This is screenreader text for the edX YouTube social media link in the footer.',
|
||||||
|
},
|
||||||
|
'footer.socialLinks.srText.linkedin': {
|
||||||
|
id: 'footer.socialLinks.srText.linkedin',
|
||||||
|
defaultMessage: 'Follow edX on LinkedIn',
|
||||||
|
description: 'This is screenreader text for the edX LinkedIn social media link in the footer.',
|
||||||
|
},
|
||||||
|
'footer.socialLinks.srText.reddit': {
|
||||||
|
id: 'footer.socialLinks.srText.reddit',
|
||||||
|
defaultMessage: 'Subscribe to the edX subreddit',
|
||||||
|
description: 'This is screenreader text for the edX reddit social media link in the footer.',
|
||||||
|
},
|
||||||
|
'footer.languageForm.select.label': {
|
||||||
|
id: 'footer.languageForm.select.label',
|
||||||
|
defaultMessage: 'Choose Language',
|
||||||
|
description: 'The label for the laguage select part of the language selection form.',
|
||||||
|
},
|
||||||
|
'footer.languageForm.submit.label': {
|
||||||
|
id: 'footer.languageForm.submit.label',
|
||||||
|
defaultMessage: 'Apply',
|
||||||
|
description: 'The label for button to submit the language selection form.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.about': {
|
||||||
|
id: 'footer.edxLinks.about',
|
||||||
|
defaultMessage: 'About',
|
||||||
|
description: 'The label for the link to the about edX page.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.business': {
|
||||||
|
id: 'footer.edxLinks.business',
|
||||||
|
defaultMessage: 'edX for Business',
|
||||||
|
description: 'The label for the link to the edX for business page.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.affiliates': {
|
||||||
|
id: 'footer.edxLinks.affiliates',
|
||||||
|
defaultMessage: 'Affiliates',
|
||||||
|
description: 'The label for the link to the edX affiliates page.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.openEdx': {
|
||||||
|
id: 'footer.edxLinks.openEdx',
|
||||||
|
defaultMessage: 'Open edX',
|
||||||
|
description: 'The label for the link to the open edX site.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.careers': {
|
||||||
|
id: 'footer.edxLinks.careers',
|
||||||
|
defaultMessage: 'Careers',
|
||||||
|
description: 'The label for the link to the edX Careers page.',
|
||||||
|
},
|
||||||
|
'footer.edxLinks.news': {
|
||||||
|
id: 'footer.edxLinks.news',
|
||||||
|
defaultMessage: 'News',
|
||||||
|
description: 'The label for the link to the edX news page.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.heading': {
|
||||||
|
id: 'footer.legalLinks.heading',
|
||||||
|
defaultMessage: 'Legal',
|
||||||
|
description: 'Heading for the legal links section of the footer.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.termsOfService': {
|
||||||
|
id: 'footer.legalLinks.termsOfService',
|
||||||
|
defaultMessage: 'Terms of Service & Honor Code',
|
||||||
|
description: 'The label for the link to the edX terms of service page.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.privacyPolicy': {
|
||||||
|
id: 'footer.legalLinks.privacyPolicy',
|
||||||
|
defaultMessage: 'Privacy Policy',
|
||||||
|
description: 'The label for the link to the edX privacy policy page.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.a11yPolicy': {
|
||||||
|
id: 'footer.legalLinks.a11yPolicy',
|
||||||
|
defaultMessage: 'Accessibility Policy',
|
||||||
|
description: 'The label for the link to the edX accessibility policy page.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.trademarkPolicy': {
|
||||||
|
id: 'footer.legalLinks.trademarkPolicy',
|
||||||
|
defaultMessage: 'Trademark Policy',
|
||||||
|
description: 'The label for the link to the edX trademark policy page.',
|
||||||
|
},
|
||||||
|
'footer.legalLinks.sitemap': {
|
||||||
|
id: 'footer.legalLinks.sitemap',
|
||||||
|
defaultMessage: 'Sitemap',
|
||||||
|
description: 'The label for the link to the edX sitemap page.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.heading': {
|
||||||
|
id: 'footer.connectLinks.heading',
|
||||||
|
defaultMessage: 'Connect',
|
||||||
|
description: 'Heading for the connect links section of the footer.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.blog': {
|
||||||
|
id: 'footer.connectLinks.blog',
|
||||||
|
defaultMessage: 'Blog',
|
||||||
|
description: 'The label for the link to the edX blog.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.contact': {
|
||||||
|
id: 'footer.connectLinks.contact',
|
||||||
|
defaultMessage: 'Contact Us',
|
||||||
|
description: 'The label for the link to the contact edX page.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.help': {
|
||||||
|
id: 'footer.connectLinks.help',
|
||||||
|
defaultMessage: 'Help Center',
|
||||||
|
description: 'The label for the link to the edX help center.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.mediaKit': {
|
||||||
|
id: 'footer.connectLinks.mediaKit',
|
||||||
|
defaultMessage: 'Media Kit',
|
||||||
|
description: 'The label for the link to the edX media kit page.',
|
||||||
|
},
|
||||||
|
'footer.connectLinks.donate': {
|
||||||
|
id: 'footer.connectLinks.donate',
|
||||||
|
defaultMessage: 'Donate',
|
||||||
|
description: 'The label for the link to the edX donation page.',
|
||||||
|
},
|
||||||
|
'footer.mobileApp.apple': {
|
||||||
|
id: 'footer.mobileApp.apple',
|
||||||
|
defaultMessage: 'Download the edX mobile app from the Apple App Store',
|
||||||
|
description: 'The label for the link to download the apple version of the edX app.',
|
||||||
|
},
|
||||||
|
'footer.mobileApp.google': {
|
||||||
|
id: 'footer.mobileApp.google',
|
||||||
|
defaultMessage: 'Download the edX mobile app from Google Play',
|
||||||
|
description: 'The label for the link to download the google version of the edX app.',
|
||||||
|
},
|
||||||
|
'footer.logo.altText': {
|
||||||
|
id: 'footer.logo.altText',
|
||||||
|
defaultMessage: 'Andal Learning',
|
||||||
|
description: 'alt text for the footer logo.',
|
||||||
|
},
|
||||||
|
'footer.logo.ariaLabel': {
|
||||||
|
id: 'footer.logo.ariaLabel',
|
||||||
|
defaultMessage: 'Andal Learning Home',
|
||||||
|
description: 'aria-label for the footer logo.',
|
||||||
|
},
|
||||||
|
'footer.ariaLabel': {
|
||||||
|
id: 'footer.ariaLabel',
|
||||||
|
defaultMessage: 'Page Footer',
|
||||||
|
description: 'aria-label for the footer component',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
122
src/components/Footer.test.jsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
|
||||||
|
import Footer from './Footer';
|
||||||
|
import FooterSlot from '../plugin-slots/FooterSlot';
|
||||||
|
import StudioFooterHelpSectionSlot from '../plugin-slots/StudioFooterHelpSectionSlot';
|
||||||
|
|
||||||
|
const FooterWithContext = ({ locale = 'es' }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: {
|
||||||
|
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
|
||||||
|
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||||
|
},
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale}>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<FooterSlot />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FooterWithLanguageSelector = ({ languageSelected = () => {} }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: {
|
||||||
|
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
|
||||||
|
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||||
|
},
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<Footer
|
||||||
|
onLanguageSelected={languageSelected}
|
||||||
|
supportedLanguages={[
|
||||||
|
{ label: 'English', value: 'en' },
|
||||||
|
{ label: 'Español', value: 'es' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('<Footer />', () => {
|
||||||
|
describe('renders correctly', () => {
|
||||||
|
it('renders without a language selector', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(<FooterWithContext locale="en" />)
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('renders without a language selector in es', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(<FooterWithContext locale="es" />)
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
it('renders with a language selector', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(<FooterWithLanguageSelector />)
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handles language switching', () => {
|
||||||
|
it('calls onLanguageSelected prop when a language is changed', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const mockHandleLanguageSelected = jest.fn();
|
||||||
|
render(<FooterWithLanguageSelector languageSelected={mockHandleLanguageSelected} />);
|
||||||
|
|
||||||
|
await user.selectOptions(screen.getByRole('combobox'), 'es');
|
||||||
|
await user.click(screen.getByTestId('site-footer-submit-btn'));
|
||||||
|
|
||||||
|
expect(mockHandleLanguageSelected).toHaveBeenCalledWith('es');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<StudioFooterHelpSectionSlot />', () => {
|
||||||
|
const SectionWithContext = ({ locale = 'es' }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: {
|
||||||
|
LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
|
||||||
|
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||||
|
},
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale}>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<StudioFooterHelpSectionSlot />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('renders correctly', () => {
|
||||||
|
const tree = renderer
|
||||||
|
.create(<SectionWithContext />)
|
||||||
|
.toJSON();
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
58
src/components/LanguageSelector.jsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const LanguageSelector = ({
|
||||||
|
options, onSubmit, ...props
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const languageCode = e.target.elements['site-footer-language-select'].value;
|
||||||
|
onSubmit(languageCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="form-inline"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="form-group">
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<label htmlFor="site-footer-language-select" className="d-inline-block m-0">
|
||||||
|
<FormattedMessage
|
||||||
|
id="footer.languageForm.select.label"
|
||||||
|
defaultMessage="Choose Language"
|
||||||
|
description="The label for the laguage select part of the language selection form."
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="site-footer-language-select"
|
||||||
|
className="form-control-sm mx-2"
|
||||||
|
name="site-footer-language-select"
|
||||||
|
defaultValue={intl.locale}
|
||||||
|
>
|
||||||
|
{options.map(({ value, label }) => <option key={value} value={value}>{label}</option>)}
|
||||||
|
</select>
|
||||||
|
<button data-testid="site-footer-submit-btn" className="btn btn-outline-primary btn-sm" type="submit">
|
||||||
|
<FormattedMessage
|
||||||
|
id="footer.languageForm.submit.label"
|
||||||
|
defaultMessage="Apply"
|
||||||
|
description="The label for button to submit the language selection form."
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
LanguageSelector.propTypes = {
|
||||||
|
onSubmit: PropTypes.func.isRequired,
|
||||||
|
options: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
value: PropTypes.string,
|
||||||
|
label: PropTypes.string,
|
||||||
|
})).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguageSelector;
|
||||||
198
src/components/__snapshots__/Footer.test.jsx.snap
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<Footer /> renders correctly renders with a language selector 1`] = `
|
||||||
|
<footer
|
||||||
|
className="footer d-flex border-top py-3 px-4"
|
||||||
|
role="contentinfo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="container-fluid d-flex"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
aria-label="edX Home"
|
||||||
|
className="d-block"
|
||||||
|
href="http://localhost:18000"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Powered by Open edX"
|
||||||
|
src="https://edx-cdn.org/v3/default/logo-trademark.svg"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"maxHeight": 45,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
className="flex-grow-1"
|
||||||
|
/>
|
||||||
|
<form
|
||||||
|
className="form-inline"
|
||||||
|
onSubmit={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="form-group"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
className="d-inline-block m-0"
|
||||||
|
htmlFor="site-footer-language-select"
|
||||||
|
>
|
||||||
|
Choose Language
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="form-control-sm mx-2"
|
||||||
|
defaultValue="en"
|
||||||
|
id="site-footer-language-select"
|
||||||
|
name="site-footer-language-select"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
value="en"
|
||||||
|
>
|
||||||
|
English
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
value="es"
|
||||||
|
>
|
||||||
|
Español
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-primary btn-sm"
|
||||||
|
data-testid="site-footer-submit-btn"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Apply
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Footer /> renders correctly renders without a language selector 1`] = `
|
||||||
|
<footer
|
||||||
|
className="footer d-flex border-top py-3 px-4"
|
||||||
|
role="contentinfo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="container-fluid d-flex"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
aria-label="edX Home"
|
||||||
|
className="d-block"
|
||||||
|
href="http://localhost:18000"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Powered by Open edX"
|
||||||
|
src="https://edx-cdn.org/v3/default/logo-trademark.svg"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"maxHeight": 45,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
className="flex-grow-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Footer /> renders correctly renders without a language selector in es 1`] = `
|
||||||
|
<footer
|
||||||
|
className="footer d-flex border-top py-3 px-4"
|
||||||
|
role="contentinfo"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="container-fluid d-flex"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
aria-label="edX Home"
|
||||||
|
className="d-block"
|
||||||
|
href="http://localhost:18000"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
alt="Powered by Open edX"
|
||||||
|
src="https://edx-cdn.org/v3/default/logo-trademark.svg"
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"maxHeight": 45,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
className="flex-grow-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<StudioFooterHelpSectionSlot /> renders correctly 1`] = `
|
||||||
|
[
|
||||||
|
<div
|
||||||
|
className="m-0 mt-6 row align-items-center justify-content-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="col border-top mr-2"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline-primary btn-sm"
|
||||||
|
data-testid="helpToggleButton"
|
||||||
|
disabled={false}
|
||||||
|
onClick={[Function]}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="pgn__icon pgn__icon__sm btn-icon-before"
|
||||||
|
>
|
||||||
|
<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="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2Zm1 17h-2v-2h2v2Zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
Looking for help with Studio?
|
||||||
|
<span
|
||||||
|
className="pgn__icon pgn__icon__sm btn-icon-after"
|
||||||
|
>
|
||||||
|
<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="M16.59 8.59 12 13.17 7.41 8.59 6 10l6 6 6-6-1.41-1.41Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="col border-top ml-2"
|
||||||
|
/>
|
||||||
|
</div>,
|
||||||
|
<div
|
||||||
|
className="px-4 container-mw-xl container-fluid"
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
`;
|
||||||
92
src/components/studio-footer/StudioFooter.jsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
import { ensureConfig } from '@edx/frontend-platform';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import {
|
||||||
|
ActionRow,
|
||||||
|
Container,
|
||||||
|
Hyperlink,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import messages from './messages';
|
||||||
|
import StudioFooterLogoSlot from '../../plugin-slots/StudioFooterLogoSlot';
|
||||||
|
import StudioFooterHelpSectionSlot from '../../plugin-slots/StudioFooterHelpSectionSlot';
|
||||||
|
|
||||||
|
ensureConfig([
|
||||||
|
'LMS_BASE_URL',
|
||||||
|
'MARKETING_SITE_BASE_URL',
|
||||||
|
'TERMS_OF_SERVICE_URL',
|
||||||
|
'PRIVACY_POLICY_URL',
|
||||||
|
'SUPPORT_EMAIL',
|
||||||
|
'SITE_NAME',
|
||||||
|
'STUDIO_BASE_URL',
|
||||||
|
'ENABLE_ACCESSIBILITY_PAGE',
|
||||||
|
], 'Studio Footer component');
|
||||||
|
|
||||||
|
const StudioFooter = ({
|
||||||
|
containerProps,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const { config } = useContext(AppContext);
|
||||||
|
|
||||||
|
const { containerClassName, ...restContainerProps } = containerProps || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<StudioFooterHelpSectionSlot containerProps={containerProps} />
|
||||||
|
<Container
|
||||||
|
size="xl"
|
||||||
|
className={classNames('px-4', containerClassName)}
|
||||||
|
{...restContainerProps}
|
||||||
|
>
|
||||||
|
<ActionRow className="pt-3 m-0 x-small">
|
||||||
|
© {new Date().getFullYear()} <Hyperlink destination={config.MARKETING_SITE_BASE_URL} target="_blank" className="ml-2">{config.SITE_NAME}</Hyperlink>
|
||||||
|
<ActionRow.Spacer />
|
||||||
|
{!isEmpty(config.TERMS_OF_SERVICE_URL) && (
|
||||||
|
<Hyperlink destination={config.TERMS_OF_SERVICE_URL} data-testid="termsOfService">
|
||||||
|
{intl.formatMessage(messages.termsOfServiceLinkLabel)}
|
||||||
|
</Hyperlink>
|
||||||
|
)}{!isEmpty(config.PRIVACY_POLICY_URL) && (
|
||||||
|
<Hyperlink destination={config.PRIVACY_POLICY_URL} data-testid="privacyPolicy">
|
||||||
|
{intl.formatMessage(messages.privacyPolicyLinkLabel)}
|
||||||
|
</Hyperlink>
|
||||||
|
)}
|
||||||
|
{config.ENABLE_ACCESSIBILITY_PAGE === 'true' && (
|
||||||
|
<Hyperlink
|
||||||
|
destination={`${config.STUDIO_BASE_URL}/accessibility`}
|
||||||
|
data-testid="accessibilityRequest"
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.accessibilityRequestLinkLabel)}
|
||||||
|
</Hyperlink>
|
||||||
|
)}
|
||||||
|
<Hyperlink destination={config.LMS_BASE_URL}>LMS</Hyperlink>
|
||||||
|
</ActionRow>
|
||||||
|
<ActionRow className="mt-3 pb-4 x-small">
|
||||||
|
{/*
|
||||||
|
Site operators: Please do not remove this paragraph! this attributes back to edX and
|
||||||
|
makes your acknowledgement of edX's trademarks clear.
|
||||||
|
Translators: 'edX' and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate
|
||||||
|
any of these trademarks and company names.
|
||||||
|
*/}
|
||||||
|
<FormattedMessage {...messages.trademarkMessage} />
|
||||||
|
<Hyperlink className="ml-1" destination="https://www.edx.org">edX Inc</Hyperlink>.
|
||||||
|
<ActionRow.Spacer />
|
||||||
|
<StudioFooterLogoSlot />
|
||||||
|
</ActionRow>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StudioFooter.propTypes = {
|
||||||
|
containerProps: PropTypes.shape(Container.propTypes),
|
||||||
|
};
|
||||||
|
|
||||||
|
StudioFooter.defaultProps = {
|
||||||
|
containerProps: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudioFooter;
|
||||||
128
src/components/studio-footer/StudioFooter.test.jsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/* eslint-disable react/prop-types */
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import StudioFooterSlot from '../../plugin-slots/StudioFooterSlot';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||||
|
MARKETING_SITE_BASE_URL: process.env.MARKETING_SITE_BASE_URL,
|
||||||
|
TERMS_OF_SERVICE_URL: process.env.TERMS_OF_SERVICE_URL,
|
||||||
|
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL,
|
||||||
|
SUPPORT_EMAIL: process.env.SUPPORT_EMAIL,
|
||||||
|
SITE_NAME: process.env.SITE_NAME,
|
||||||
|
STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
|
||||||
|
ENABLE_ACCESSIBILITY_PAGE: process.env.ENABLE_ACCESSIBILITY_PAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentConfig = config;
|
||||||
|
const Component = ({ updateVariable }) => {
|
||||||
|
if (updateVariable) {
|
||||||
|
const [variable, value] = updateVariable;
|
||||||
|
currentConfig = {
|
||||||
|
...config,
|
||||||
|
[variable]: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: currentConfig,
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale="en">
|
||||||
|
<AppContext.Provider value={contextValue}>
|
||||||
|
<StudioFooterSlot />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.unmock('@openedx/paragon');
|
||||||
|
|
||||||
|
describe('Footer', () => {
|
||||||
|
describe('help section default view', () => {
|
||||||
|
it('help button should read Looking for help with Studio?', () => {
|
||||||
|
render(<Component />);
|
||||||
|
expect(screen.getByText(messages.openHelpButtonLabel.defaultMessage))
|
||||||
|
.toBeVisible();
|
||||||
|
});
|
||||||
|
it('help button link row should not be visible', () => {
|
||||||
|
render(<Component />);
|
||||||
|
expect(screen.queryByTestId('helpButtonRow')).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('help section expanded view', () => {
|
||||||
|
it('help button should read Hide Studio help', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Component />);
|
||||||
|
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
|
||||||
|
await user.click(helpToggleButton);
|
||||||
|
expect(screen.getByText(messages.closeHelpButtonLabel.defaultMessage))
|
||||||
|
.toBeVisible();
|
||||||
|
});
|
||||||
|
it('help button link row should be visible', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Component />);
|
||||||
|
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
|
||||||
|
await user.click(helpToggleButton);
|
||||||
|
expect(screen.getByTestId('helpButtonRow')).toBeVisible();
|
||||||
|
});
|
||||||
|
it('Open edX portal button should be visible', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Component />);
|
||||||
|
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
|
||||||
|
await user.click(helpToggleButton);
|
||||||
|
expect(screen.getByTestId('openEdXDemoCourseButton')).toBeVisible();
|
||||||
|
});
|
||||||
|
it('should not show contact us button', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Component />);
|
||||||
|
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
|
||||||
|
await user.click(helpToggleButton);
|
||||||
|
expect(screen.queryByTestId('contactUsButton')).toBeNull();
|
||||||
|
});
|
||||||
|
it('should show contact us button', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<Component updateVariable={['SUPPORT_EMAIL', 'support@email.com']} />);
|
||||||
|
const helpToggleButton = screen.getByText(messages.openHelpButtonLabel.defaultMessage);
|
||||||
|
await user.click(helpToggleButton);
|
||||||
|
expect(screen.getByTestId('contactUsButton')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('policy link row', () => {
|
||||||
|
it('should only show LMS link', () => {
|
||||||
|
render(<Component />);
|
||||||
|
expect(screen.getByText('LMS')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('termsOfService')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('privacyPolicy')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('accessibilityRequest')).toBeNull();
|
||||||
|
});
|
||||||
|
it('should show terms of service link', () => {
|
||||||
|
render(<Component updateVariable={['TERMS_OF_SERVICE_URL', 'termsofserviceurl']} />);
|
||||||
|
expect(screen.getByText('LMS')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('termsOfService')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('privacyPolicy')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('accessibilityRequest')).toBeNull();
|
||||||
|
});
|
||||||
|
it('should show privacy policy link', () => {
|
||||||
|
render(<Component updateVariable={['PRIVACY_POLICY_URL', 'privacypolicyurl']} />);
|
||||||
|
expect(screen.getByText('LMS')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('termsOfService')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('privacyPolicy')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('accessibilityRequest')).toBeNull();
|
||||||
|
});
|
||||||
|
it('should show accessibilty request link', () => {
|
||||||
|
render(<Component updateVariable={['ENABLE_ACCESSIBILITY_PAGE', 'true']} />);
|
||||||
|
expect(screen.getByText('LMS')).toBeVisible();
|
||||||
|
expect(screen.queryByTestId('termsOfService')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('privacyPolicy')).toBeNull();
|
||||||
|
expect(screen.queryByTestId('accessibilityRequest')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
32
src/components/studio-footer/help-components/HelpButton.jsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import { ExpandLess, ExpandMore, Help } from '@openedx/paragon/icons';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import messages from '../messages';
|
||||||
|
|
||||||
|
const HelpButton = ({ isOpen, setIsOpen }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
data-testid="helpToggleButton"
|
||||||
|
variant="outline-primary"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
iconBefore={Help}
|
||||||
|
iconAfter={isOpen ? ExpandLess : ExpandMore}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
{isOpen ? intl.formatMessage(messages.closeHelpButtonLabel)
|
||||||
|
: intl.formatMessage(messages.openHelpButtonLabel)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HelpButton.propTypes = {
|
||||||
|
setIsOpen: PropTypes.func.isRequired,
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HelpButton;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import HelpButton from './HelpButton';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const ButtonWithContext = ({ locale = 'en', isOpen, setIsOpen }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config: { },
|
||||||
|
}), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale}>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<HelpButton isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('HelpButton', () => {
|
||||||
|
const mockSetIsOpen = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockSetIsOpen.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with "open" label when isOpen is false', () => {
|
||||||
|
render(<ButtonWithContext isOpen={false} setIsOpen={mockSetIsOpen} />);
|
||||||
|
expect(screen.getByTestId('helpToggleButton')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button')).toHaveTextContent(/help|open/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with "close" label when isOpen is true', () => {
|
||||||
|
render(<ButtonWithContext isOpen setIsOpen={mockSetIsOpen} />);
|
||||||
|
expect(screen.getByTestId('helpToggleButton')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button')).toHaveTextContent(/close|help/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls setIsOpen with the toggled value when clicked', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const { rerender } = render(<ButtonWithContext isOpen={false} setIsOpen={mockSetIsOpen} />);
|
||||||
|
await user.click(screen.getByTestId('helpToggleButton'));
|
||||||
|
expect(mockSetIsOpen).toHaveBeenCalledWith(true);
|
||||||
|
rerender(<ButtonWithContext isOpen setIsOpen={mockSetIsOpen} />);
|
||||||
|
await user.click(screen.getByTestId('helpToggleButton'));
|
||||||
|
expect(mockSetIsOpen).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
62
src/components/studio-footer/help-components/HelpContent.jsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import { ensureConfig } from '@edx/frontend-platform';
|
||||||
|
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||||
|
import {
|
||||||
|
ActionRow,
|
||||||
|
Button,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import messages from '../messages';
|
||||||
|
|
||||||
|
ensureConfig([
|
||||||
|
'SUPPORT_EMAIL',
|
||||||
|
], 'Studio Footer Help Content component');
|
||||||
|
|
||||||
|
const BUTTONS = [
|
||||||
|
{
|
||||||
|
as: 'a',
|
||||||
|
href: 'https://docs.openedx.org/en/latest/educators/quickstarts/build_a_course.html',
|
||||||
|
size: 'sm',
|
||||||
|
message: messages.educatorsDocsButtonLabel,
|
||||||
|
dataTestid: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
as: 'a',
|
||||||
|
href: 'https://training.openedx.io/courses/course-v1:OpenedX+DemoX+Demo_Course/about',
|
||||||
|
size: 'sm',
|
||||||
|
message: messages.openEdxDemoCourseButtonLabel,
|
||||||
|
dataTestid: 'openEdXDemoCourseButton',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const HelpContent = () => {
|
||||||
|
const { config } = useContext(AppContext);
|
||||||
|
return (
|
||||||
|
<ActionRow key="help-link-button-row" className="py-4" data-testid="helpButtonRow">
|
||||||
|
<ActionRow.Spacer />
|
||||||
|
|
||||||
|
{BUTTONS.map(({
|
||||||
|
as, href, size, message, dataTestid,
|
||||||
|
}) => (
|
||||||
|
<Button as={as} href={href} size={size} key={message.id} data-testid={dataTestid}>
|
||||||
|
<FormattedMessage {...message} />
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{!isEmpty(config.SUPPORT_EMAIL) && (
|
||||||
|
<Button
|
||||||
|
as="a"
|
||||||
|
href={`mailto:${config.SUPPORT_EMAIL}`}
|
||||||
|
size="sm"
|
||||||
|
data-testid="contactUsButton"
|
||||||
|
>
|
||||||
|
<FormattedMessage {...messages.contactUsButtonLabel} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<ActionRow.Spacer />
|
||||||
|
</ActionRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HelpContent;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import HelpContent from './HelpContent';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import messages from '../messages';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const ContentWithContext = ({ locale = 'en', config = {} }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config,
|
||||||
|
}), [config]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale}>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<HelpContent />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('HelpContent', () => {
|
||||||
|
it('renders all help buttons', () => {
|
||||||
|
const config = { SUPPORT_EMAIL: 'support@example.com' };
|
||||||
|
render(<ContentWithContext config={config} />);
|
||||||
|
expect(screen.getByText(messages.educatorsDocsButtonLabel.defaultMessage)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(messages.openEdxDemoCourseButtonLabel.defaultMessage)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render contact button if SUPPORT_EMAIL is empty', () => {
|
||||||
|
render(<ContentWithContext config={{ SUPPORT_EMAIL: null }} />);
|
||||||
|
expect(screen.queryByTestId('contactUsButton')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders ActionRow with correct test id', () => {
|
||||||
|
render(<ContentWithContext />);
|
||||||
|
expect(screen.getByTestId('helpButtonRow')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/components/studio-footer/help-components/HelpSection.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
TransitionReplace,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import StudioFooterHelpButtonSlot from '../../../plugin-slots/StudioFooterHelpButtonSlot';
|
||||||
|
import StudioFooterHelpContentlot from '../../../plugin-slots/StudioFooterHelpContentSlot';
|
||||||
|
|
||||||
|
const HelpSection = ({ containerProps }) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const { containerClassName, ...restContainerProps } = containerProps || {};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="m-0 mt-6 row align-items-center justify-content-center">
|
||||||
|
<div className="col border-top mr-2" />
|
||||||
|
<StudioFooterHelpButtonSlot
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
isOpen={isOpen}
|
||||||
|
/>
|
||||||
|
<div className="col border-top ml-2" />
|
||||||
|
</div>
|
||||||
|
<Container
|
||||||
|
size="xl"
|
||||||
|
className={classNames('px-4', containerClassName)}
|
||||||
|
{...restContainerProps}
|
||||||
|
>
|
||||||
|
{isOpen && <TransitionReplace><StudioFooterHelpContentlot /></TransitionReplace> }
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
HelpSection.propTypes = {
|
||||||
|
containerProps: PropTypes.shape(Container.propTypes),
|
||||||
|
};
|
||||||
|
|
||||||
|
HelpSection.defaultProps = {
|
||||||
|
containerProps: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HelpSection;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||||
|
import { AppContext } from '@edx/frontend-platform/react';
|
||||||
|
import HelpSection from './HelpSection';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/prop-types
|
||||||
|
const HelpSectionWithContext = ({ locale = 'en', config = {}, containerProps = null }) => {
|
||||||
|
const contextValue = useMemo(() => ({
|
||||||
|
authenticatedUser: null,
|
||||||
|
config,
|
||||||
|
}), [config]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale}>
|
||||||
|
<AppContext.Provider
|
||||||
|
value={contextValue}
|
||||||
|
>
|
||||||
|
<HelpSection containerProps={containerProps} />
|
||||||
|
</AppContext.Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('HelpSection', () => {
|
||||||
|
it('renders the HelpButton', () => {
|
||||||
|
render(<HelpSectionWithContext />);
|
||||||
|
expect(screen.getByTestId('helpToggleButton')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not show HelpContent by default', () => {
|
||||||
|
render(<HelpSectionWithContext />);
|
||||||
|
expect(screen.queryByTestId('helpButtonRow')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows HelpContent when HelpButton is clicked', () => {
|
||||||
|
render(<HelpSectionWithContext />);
|
||||||
|
const button = screen.getByTestId('helpToggleButton');
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(screen.getByTestId('helpButtonRow')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides HelpContent when HelpButton is clicked twice', () => {
|
||||||
|
render(<HelpSectionWithContext containerProps={{ containerClassName: 'container', size: 'xl' }} />);
|
||||||
|
const button = screen.getByTestId('helpToggleButton');
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(screen.queryByTestId('helpButtonRow')).toBeInTheDocument();
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(screen.queryByTestId('helpButtonRow')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
3
src/components/studio-footer/index.jsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import StudioFooter from './StudioFooter';
|
||||||
|
|
||||||
|
export default StudioFooter;
|
||||||
51
src/components/studio-footer/messages.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
openHelpButtonLabel: {
|
||||||
|
id: 'authoring.footer.help.openHelp.button.label',
|
||||||
|
defaultMessage: 'Looking for help with Studio?',
|
||||||
|
description: 'Label for button that opens the collapsed section with help buttons',
|
||||||
|
},
|
||||||
|
closeHelpButtonLabel: {
|
||||||
|
id: 'authoring.footer.help.closeHelp.button.label',
|
||||||
|
defaultMessage: 'Hide Studio help',
|
||||||
|
description: 'Label for button that closes the collapsed section with help buttons',
|
||||||
|
},
|
||||||
|
educatorsDocsButtonLabel: {
|
||||||
|
id: 'authoring.footer.help.educatorsDocs.button.label',
|
||||||
|
defaultMessage: 'Open edX Educators Docs',
|
||||||
|
description: 'Label for button that links to the build a course quickstart site',
|
||||||
|
},
|
||||||
|
openEdxDemoCourseButtonLabel: {
|
||||||
|
id: 'authoring.footer.help.openEdxDemoCourse.button.label',
|
||||||
|
defaultMessage: 'Open edX Demo Course',
|
||||||
|
description: 'Label for button that links to the open edX demo course portal',
|
||||||
|
},
|
||||||
|
contactUsButtonLabel: {
|
||||||
|
id: 'authoring.footer.help.contactUs.button.label',
|
||||||
|
defaultMessage: 'Contact us',
|
||||||
|
description: 'Label for button that links to the email for partner support',
|
||||||
|
},
|
||||||
|
termsOfServiceLinkLabel: {
|
||||||
|
id: 'authoring.footer.termsOfService.link.label',
|
||||||
|
defaultMessage: 'Terms of Service',
|
||||||
|
description: 'Label for button that links to the terms of service page',
|
||||||
|
},
|
||||||
|
privacyPolicyLinkLabel: {
|
||||||
|
id: 'authoring.footer.privacyPolicy.link.label',
|
||||||
|
defaultMessage: 'Privacy Policy',
|
||||||
|
description: 'Label for button that links to the privacy policy page',
|
||||||
|
},
|
||||||
|
accessibilityRequestLinkLabel: {
|
||||||
|
id: 'authoring.footer.accessibilityRequest.link.label',
|
||||||
|
defaultMessage: 'Accessibility Accomodation Request',
|
||||||
|
description: 'Label for button that links to the accessibility accomodation requests page',
|
||||||
|
},
|
||||||
|
trademarkMessage: {
|
||||||
|
id: 'authoring.footer.trademark.message',
|
||||||
|
defaultMessage: 'edX and Open edX, and the edX and Open edX logos are registered trademarks of',
|
||||||
|
description: 'Message about the use of logos and names edX and Open edX',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default messages;
|
||||||
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* This code originally came from https://github.com/efischer19/reactifex/blob/master/main.js,
|
|
||||||
* which should be edx/reactifex. It is temporarily being copied here until we find it a new home.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: This script is called from Jenkins using devDependencies, so eslint is being
|
|
||||||
// disabled so it doesn't force you to make these real dependencies.
|
|
||||||
const fs = require('fs'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const glob = require('glob'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
const path = require('path'); // eslint-disable-line import/no-extraneous-dependencies
|
|
||||||
|
|
||||||
// Expected input: a directory, possibly containing subdirectories, with .json files. Each .json
|
|
||||||
// file is an array of translation triplets (id, description, defaultMessage).
|
|
||||||
function gatherJson(dir) {
|
|
||||||
const ret = [];
|
|
||||||
const files = glob.sync(`${dir}/**/*.json`);
|
|
||||||
|
|
||||||
files.forEach((filename) => {
|
|
||||||
const messages = JSON.parse(fs.readFileSync(filename));
|
|
||||||
ret.push(...messages);
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
const jsonDir = process.argv[2];
|
|
||||||
const messageObjects = gatherJson(jsonDir);
|
|
||||||
|
|
||||||
if (process.argv[3] === '--comments') { // prepare to handle the translator notes
|
|
||||||
const thisFile = path.basename(`${__filename}`);
|
|
||||||
const bashScriptsPath = './node_modules/reactifex/bash_scripts';
|
|
||||||
|
|
||||||
process.stdout.write(`${thisFile}: generating bash scripts...\n`);
|
|
||||||
process.stdout.write(`${thisFile}: info file at ${bashScriptsPath}/hashmap.json\n`);
|
|
||||||
|
|
||||||
const messageInfo = JSON.parse(fs.readFileSync(`${bashScriptsPath}/hashmap.json`));
|
|
||||||
const dataPath = `${bashScriptsPath}/hashed_data.txt`;
|
|
||||||
|
|
||||||
process.stdout.write(`${thisFile}: data path is ${dataPath}\n`);
|
|
||||||
fs.writeFileSync(dataPath, '');
|
|
||||||
|
|
||||||
messageObjects.forEach((message) => {
|
|
||||||
const info = messageInfo.find(mi => mi.key === message.id);
|
|
||||||
if (info) {
|
|
||||||
fs.appendFileSync(dataPath, `${info.string_hash}|${message.description}\n`);
|
|
||||||
} else {
|
|
||||||
process.stdout.write(`${thisFile}: string ${message.id} does not yet exist on transifex!\n`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const output = {};
|
|
||||||
|
|
||||||
messageObjects.forEach((message) => {
|
|
||||||
output[message.id] = message.defaultMessage;
|
|
||||||
});
|
|
||||||
fs.writeFileSync(process.argv[3], JSON.stringify(output, null, 2));
|
|
||||||
}
|
|
||||||
1
src/i18n/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {};
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"footer.site-footer.link.about": "About",
|
|
||||||
"footer.site-footer.link.business": "{siteName} for Business",
|
|
||||||
"footer.site-footer.link.affiliates": "Affiliates",
|
|
||||||
"footer.site-footer.link.open-source": "Open {siteName}",
|
|
||||||
"footer.site-footer.link.careers": "Careers",
|
|
||||||
"footer.site-footer.link.news": "News",
|
|
||||||
"footer.site-footer.link.header.legal": "Legal",
|
|
||||||
"footer.site-footer.link.terms-of-service": "Terms of Service & Honor Code",
|
|
||||||
"footer.site-footer.link.privacy": "Privacy Policy",
|
|
||||||
"footer.site-footer.link.accessibility": "Accessibility Policy",
|
|
||||||
"footer.site-footer.link.trademark": "Trademark Policy",
|
|
||||||
"footer.site-footer.link.sitemap": "Sitemap",
|
|
||||||
"footer.site-footer.link.header.connect": "Connect",
|
|
||||||
"footer.site-footer.link.blog": "Blog",
|
|
||||||
"footer.site-footer.link.contact-us": "Contact Us",
|
|
||||||
"footer.site-footer.link.help-center": "Help Center",
|
|
||||||
"footer.site-footer.link.media-kit": "Media Kit",
|
|
||||||
"footer.site-footer.link.donate": "Donate",
|
|
||||||
"footer.site-footer.copyright-text": "{copyrightSymbol} {startDate}–{endDate} {siteName} Inc.",
|
|
||||||
"footer.site-footer.trademark-text": "EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | {icpLicense}",
|
|
||||||
"footer.site-footer.site-logo.alt-text": "{siteName} logo",
|
|
||||||
"footer.site-footer.site-logo.aria-label": "{siteName} Home",
|
|
||||||
"footer.site-footer.facebook.title": "Facebook",
|
|
||||||
"footer.site-footer.facebook.screen-reader-text": "Like {siteName} on Facebook",
|
|
||||||
"footer.site-footer.twitter.title": "Twitter",
|
|
||||||
"footer.site-footer.twitter.screen-reader-text": "Follow {siteName} on Twitter",
|
|
||||||
"footer.site-footer.youtube.title": "Youtube",
|
|
||||||
"footer.site-footer.youtube.screen-reader-text": "Subscribe to the {siteName} YouTube channel",
|
|
||||||
"footer.site-footer.linkedin.title": "LinkedIn",
|
|
||||||
"footer.site-footer.linkedin.screen-reader-text": "Follow {siteName} on LinkedIn",
|
|
||||||
"footer.site-footer.google-plus.title": "Google+",
|
|
||||||
"footer.site-footer.google-plus.screen-reader-text": "Follow {siteName} on Google+",
|
|
||||||
"footer.site-footer.reddit.title": "Reddit",
|
|
||||||
"footer.site-footer.reddit.screen-reader-text": "Subscribe to the {siteName} subreddit",
|
|
||||||
"footer.site-footer.apple-app-store.alt-text": "Download the {siteName} mobile app from the Apple App Store",
|
|
||||||
"footer.site-footer.google-play.alt-text": "Download the {siteName} mobile app from Google Play",
|
|
||||||
"footer.site-footer.footer.aria-label": "Page Footer"
|
|
||||||
}
|
|
||||||
10
src/index.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Footer, { EVENT_NAMES } from './components/Footer';
|
||||||
|
import messages from './i18n/index';
|
||||||
|
import StudioFooter from './components/studio-footer';
|
||||||
|
import FooterSlot from './plugin-slots/FooterSlot';
|
||||||
|
import StudioFooterSlot from './plugin-slots/StudioFooterSlot';
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
|
export {
|
||||||
|
messages, EVENT_NAMES, StudioFooter, FooterSlot, StudioFooterSlot,
|
||||||
|
};
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from 'react-dom';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
import SiteFooter from './lib';
|
|
||||||
import './index.scss';
|
|
||||||
|
|
||||||
import FooterLogo from './edx-footer.png';
|
|
||||||
|
|
||||||
const App = () => (
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<SiteFooter
|
|
||||||
siteName="edX"
|
|
||||||
siteLogo={FooterLogo}
|
|
||||||
marketingSiteBaseUrl="https://www.example.com"
|
|
||||||
supportUrl="https://www.example.com/support"
|
|
||||||
contactUrl="https://www.example.com/contact"
|
|
||||||
openSourceUrl="https://www.example.com/open"
|
|
||||||
termsOfServiceUrl="https://www.example.com/terms-of-service"
|
|
||||||
privacyPolicyUrl="https://www.example.com/privacy-policy"
|
|
||||||
facebookUrl="https://www.facebook.com"
|
|
||||||
twitterUrl="https://www.twitter.com"
|
|
||||||
youTubeUrl="https://www.youtube.com"
|
|
||||||
linkedInUrl="https://www.linkedin.com"
|
|
||||||
googlePlusUrl="https://plus.google.com"
|
|
||||||
redditUrl="https://reddit.com"
|
|
||||||
appleAppStoreUrl="https://store.apple.com"
|
|
||||||
googlePlayUrl="https://play.google.com"
|
|
||||||
handleAllTrackEvents={() => {}}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
render(<App />, document.getElementById('root'));
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
@import "~@edx/edx-bootstrap/sass/edx/theme";
|
|
||||||
@import "~bootstrap/scss/bootstrap";
|
|
||||||
|
|
||||||
@import './lib/scss/_site-footer.scss';
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
import { defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
'footer.site-footer.site-logo.alt-text': {
|
|
||||||
id: 'footer.site-footer.site-logo.alt-text',
|
|
||||||
defaultMessage: '{siteName} logo',
|
|
||||||
description: 'The alt description of the site logo',
|
|
||||||
},
|
|
||||||
'footer.site-footer.site-logo.aria-label': {
|
|
||||||
id: 'footer.site-footer.site-logo.aria-label',
|
|
||||||
defaultMessage: '{siteName} Home',
|
|
||||||
description: 'Aria label for the site logo which goes to the marketing site',
|
|
||||||
},
|
|
||||||
'footer.site-footer.facebook.title': {
|
|
||||||
id: 'footer.site-footer.facebook.title',
|
|
||||||
defaultMessage: 'Facebook',
|
|
||||||
description: 'Facebook button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.facebook.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.facebook.screen-reader-text',
|
|
||||||
defaultMessage: 'Like {siteName} on Facebook',
|
|
||||||
description: 'Facebook button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.twitter.title': {
|
|
||||||
id: 'footer.site-footer.twitter.title',
|
|
||||||
defaultMessage: 'Twitter',
|
|
||||||
description: 'Twitter button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.twitter.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.twitter.screen-reader-text',
|
|
||||||
defaultMessage: 'Follow {siteName} on Twitter',
|
|
||||||
description: 'Twitter button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.youtube.title': {
|
|
||||||
id: 'footer.site-footer.youtube.title',
|
|
||||||
defaultMessage: 'Youtube',
|
|
||||||
description: 'Youtube button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.youtube.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.youtube.screen-reader-text',
|
|
||||||
defaultMessage: 'Subscribe to the {siteName} YouTube channel',
|
|
||||||
description: 'Youtube button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.linkedin.title': {
|
|
||||||
id: 'footer.site-footer.linkedin.title',
|
|
||||||
defaultMessage: 'LinkedIn',
|
|
||||||
description: 'LinkedIn button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.linkedin.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.linkedin.screen-reader-text',
|
|
||||||
defaultMessage: 'Follow {siteName} on LinkedIn',
|
|
||||||
description: 'LinkedIn button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.google-plus.title': {
|
|
||||||
id: 'footer.site-footer.google-plus.title',
|
|
||||||
defaultMessage: 'Google+',
|
|
||||||
description: 'Google+ button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.google-plus.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.google-plus.screen-reader-text',
|
|
||||||
defaultMessage: 'Follow {siteName} on Google+',
|
|
||||||
description: 'Google+ button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.reddit.title': {
|
|
||||||
id: 'footer.site-footer.reddit.title',
|
|
||||||
defaultMessage: 'Reddit',
|
|
||||||
description: 'Reddit button title',
|
|
||||||
},
|
|
||||||
'footer.site-footer.reddit.screen-reader-text': {
|
|
||||||
id: 'footer.site-footer.reddit.screen-reader-text',
|
|
||||||
defaultMessage: 'Subscribe to the {siteName} subreddit',
|
|
||||||
description: 'Reddit button screen reader text',
|
|
||||||
},
|
|
||||||
'footer.site-footer.apple-app-store.alt-text': {
|
|
||||||
id: 'footer.site-footer.apple-app-store.alt-text',
|
|
||||||
defaultMessage: 'Download the {siteName} mobile app from the Apple App Store',
|
|
||||||
description: 'Apple App Store button alt description',
|
|
||||||
},
|
|
||||||
'footer.site-footer.google-play.alt-text': {
|
|
||||||
id: 'footer.site-footer.google-play.alt-text',
|
|
||||||
defaultMessage: 'Download the {siteName} mobile app from Google Play',
|
|
||||||
description: 'Google Play button alt description',
|
|
||||||
},
|
|
||||||
'footer.site-footer.footer.aria-label': {
|
|
||||||
id: 'footer.site-footer.footer.aria-label',
|
|
||||||
defaultMessage: 'Page Footer',
|
|
||||||
description: 'Aria label for the footer',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default messages;
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import renderer from 'react-test-renderer';
|
|
||||||
import { mount } from 'enzyme';
|
|
||||||
import { IntlProvider } from 'react-intl';
|
|
||||||
|
|
||||||
import FooterLogo from '../../../edx-footer.png';
|
|
||||||
import SiteFooter, { EVENT_NAMES } from './index';
|
|
||||||
|
|
||||||
const completeSiteFooterComponent = mockHandleAllTrackEvents =>
|
|
||||||
(
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<SiteFooter
|
|
||||||
siteName="example"
|
|
||||||
siteLogo={FooterLogo}
|
|
||||||
marketingSiteBaseUrl="https://www.example.com"
|
|
||||||
supportUrl="https://www.example.com/support"
|
|
||||||
contactUrl="https://www.example.com/contact"
|
|
||||||
openSourceUrl="https://www.example.com/open"
|
|
||||||
termsOfServiceUrl="https://www.example.com/terms-of-service"
|
|
||||||
privacyPolicyUrl="https://www.example.com/privacy-policy"
|
|
||||||
facebookUrl="https://www.facebook.com"
|
|
||||||
twitterUrl="https://www.twitter.com"
|
|
||||||
youTubeUrl="https://www.youtube.com"
|
|
||||||
linkedInUrl="https://www.linkedin.com"
|
|
||||||
googlePlusUrl="https://plus.google.com"
|
|
||||||
redditUrl="https://reddit.com"
|
|
||||||
appleAppStoreUrl="https://store.apple.com"
|
|
||||||
googlePlayUrl="https://play.google.com"
|
|
||||||
handleAllTrackEvents={mockHandleAllTrackEvents}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
describe('<SiteFooter />', () => {
|
|
||||||
describe('renders correctly', () => {
|
|
||||||
it('renders with social and mobile links', () => {
|
|
||||||
const mockHandleAllTrackEvents = jest.fn();
|
|
||||||
const tree = renderer
|
|
||||||
.create(completeSiteFooterComponent(mockHandleAllTrackEvents))
|
|
||||||
.toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render social links', () => {
|
|
||||||
const tree = renderer
|
|
||||||
.create((
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<SiteFooter
|
|
||||||
siteName="example"
|
|
||||||
siteLogo={FooterLogo}
|
|
||||||
marketingSiteBaseUrl="https://www.example.com"
|
|
||||||
supportUrl="https://www.example.com/support"
|
|
||||||
contactUrl="https://www.example.com/contact"
|
|
||||||
openSourceUrl="https://www.example.com/open"
|
|
||||||
termsOfServiceUrl="https://www.example.com/terms-of-service"
|
|
||||||
privacyPolicyUrl="https://www.example.com/privacy-policy"
|
|
||||||
facebookUrl="https://www.facebook.com"
|
|
||||||
twitterUrl="https://www.twitter.com"
|
|
||||||
youTubeUrl="https://www.youtube.com"
|
|
||||||
linkedInUrl="https://www.linkedin.com"
|
|
||||||
googlePlusUrl="https://plus.google.com"
|
|
||||||
redditUrl="https://reddit.com"
|
|
||||||
appleAppStoreUrl="https://store.apple.com"
|
|
||||||
googlePlayUrl="https://play.google.com"
|
|
||||||
handleAllTrackEvents={jest.fn()}
|
|
||||||
showSocialLinks={false}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not render mobile links', () => {
|
|
||||||
const tree = renderer
|
|
||||||
.create((
|
|
||||||
<IntlProvider locale="en">
|
|
||||||
<SiteFooter
|
|
||||||
siteName="example"
|
|
||||||
siteLogo={FooterLogo}
|
|
||||||
marketingSiteBaseUrl="https://www.example.com"
|
|
||||||
supportUrl="https://www.example.com/support"
|
|
||||||
contactUrl="https://www.example.com/contact"
|
|
||||||
openSourceUrl="https://www.example.com/open"
|
|
||||||
termsOfServiceUrl="https://www.example.com/terms-of-service"
|
|
||||||
privacyPolicyUrl="https://www.example.com/privacy-policy"
|
|
||||||
facebookUrl="https://www.facebook.com"
|
|
||||||
twitterUrl="https://www.twitter.com"
|
|
||||||
youTubeUrl="https://www.youtube.com"
|
|
||||||
linkedInUrl="https://www.linkedin.com"
|
|
||||||
googlePlusUrl="https://plus.google.com"
|
|
||||||
redditUrl="https://reddit.com"
|
|
||||||
appleAppStoreUrl="https://store.apple.com"
|
|
||||||
googlePlayUrl="https://play.google.com"
|
|
||||||
handleAllTrackEvents={jest.fn()}
|
|
||||||
showMobileLinks={false}
|
|
||||||
/>
|
|
||||||
</IntlProvider>
|
|
||||||
)).toJSON();
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handles analytics', () => {
|
|
||||||
it('calls handleAllTrackEvents prop when external links clicked', () => {
|
|
||||||
const mockHandleAllTrackEvents = jest.fn();
|
|
||||||
const footer = mount((completeSiteFooterComponent(mockHandleAllTrackEvents)));
|
|
||||||
const externalLinks = footer.find("a[target='_blank']");
|
|
||||||
|
|
||||||
expect(externalLinks).toHaveLength(8);
|
|
||||||
externalLinks.forEach((externalLink) => {
|
|
||||||
const callIndex = mockHandleAllTrackEvents.mock.calls.length;
|
|
||||||
externalLink.simulate('click');
|
|
||||||
expect(mockHandleAllTrackEvents.mock.calls[callIndex]).toEqual([
|
|
||||||
EVENT_NAMES.FOOTER_LINK,
|
|
||||||
{
|
|
||||||
category: 'outbound_link',
|
|
||||||
label: externalLink.prop('href'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,979 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`<SiteFooter /> renders correctly does not render mobile links 1`] = `
|
|
||||||
<footer
|
|
||||||
aria-label="Page Footer"
|
|
||||||
className="footer d-flex justify-content-center border-top py-3 px-4"
|
|
||||||
role="contentinfo"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-width-1180 d-grid"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="area-1"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
aria-label="example Home"
|
|
||||||
href="https://www.example.com/"
|
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="example logo"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-2"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
example
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/about-us"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
About
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/enterprise"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
example for Business
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/affiliate-program"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Affiliates
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/open"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Open example
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/careers"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Careers
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/news-announcements"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
News
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-3"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Legal
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/terms-of-service"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Terms of Service & Honor Code
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/privacy-policy"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Privacy Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/accessibility"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Accessibility Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/trademarks"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Trademark Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/sitemap"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Sitemap
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-4"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Connect
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/blog"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Blog
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/contact"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Contact Us
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/support"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Help Center
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/media-kit"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Media Kit
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/donate"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Donate
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-5"
|
|
||||||
>
|
|
||||||
<ul
|
|
||||||
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.facebook.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Facebook"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-facebook-square fa-2x"
|
|
||||||
id="edx-footer-icon-facebook"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Like example on Facebook
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.twitter.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Twitter"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-twitter-square fa-2x"
|
|
||||||
id="edx-footer-icon-twitter"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on Twitter
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.youtube.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Youtube"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-youtube-square fa-2x"
|
|
||||||
id="edx-footer-icon-youtube"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Subscribe to the example YouTube channel
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.linkedin.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="LinkedIn"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-linkedin-square fa-2x"
|
|
||||||
id="edx-footer-icon-linkedin"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on LinkedIn
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://plus.google.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Google+"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-google-plus-square fa-2x"
|
|
||||||
id="edx-footer-icon-google"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on Google+
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://reddit.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Reddit"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-reddit-square fa-2x"
|
|
||||||
id="edx-footer-icon-reddit"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Subscribe to the example subreddit
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<span>
|
|
||||||
© 2012–2019 example Inc.
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span>
|
|
||||||
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<SiteFooter /> renders correctly does not render social links 1`] = `
|
|
||||||
<footer
|
|
||||||
aria-label="Page Footer"
|
|
||||||
className="footer d-flex justify-content-center border-top py-3 px-4"
|
|
||||||
role="contentinfo"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-width-1180 d-grid"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="area-1"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
aria-label="example Home"
|
|
||||||
href="https://www.example.com/"
|
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="example logo"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-2"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
example
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/about-us"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
About
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/enterprise"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
example for Business
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/affiliate-program"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Affiliates
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/open"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Open example
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/careers"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Careers
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/news-announcements"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
News
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-3"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Legal
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/terms-of-service"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Terms of Service & Honor Code
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/privacy-policy"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Privacy Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/accessibility"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Accessibility Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/trademarks"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Trademark Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/sitemap"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Sitemap
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-4"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Connect
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/blog"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Blog
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/contact"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Contact Us
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/support"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Help Center
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/media-kit"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Media Kit
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/donate"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Donate
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-5"
|
|
||||||
>
|
|
||||||
<ul
|
|
||||||
className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://store.apple.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Download the example mobile app from the Apple App Store"
|
|
||||||
className="max-height-39"
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://play.google.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Download the example mobile app from Google Play"
|
|
||||||
className="max-height-39"
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<span>
|
|
||||||
© 2012–2019 example Inc.
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span>
|
|
||||||
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`<SiteFooter /> renders correctly renders with social and mobile links 1`] = `
|
|
||||||
<footer
|
|
||||||
aria-label="Page Footer"
|
|
||||||
className="footer d-flex justify-content-center border-top py-3 px-4"
|
|
||||||
role="contentinfo"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-width-1180 d-grid"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="area-1"
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
aria-label="example Home"
|
|
||||||
href="https://www.example.com/"
|
|
||||||
onClick={[Function]}
|
|
||||||
target="_self"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="example logo"
|
|
||||||
src="test-file-stub"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-2"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
example
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/about-us"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
About
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/enterprise"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
example for Business
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/affiliate-program"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Affiliates
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/open"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Open example
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/careers"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Careers
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/news-announcements"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
News
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-3"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Legal
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/terms-of-service"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Terms of Service & Honor Code
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/privacy-policy"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Privacy Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/accessibility"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Accessibility Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/trademarks"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Trademark Policy
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/sitemap"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Sitemap
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-4"
|
|
||||||
>
|
|
||||||
<h2>
|
|
||||||
<span>
|
|
||||||
Connect
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
<ul
|
|
||||||
className="list-unstyled p-0 m-0"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/blog"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Blog
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/contact"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Contact Us
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/support"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Help Center
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/media-kit"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Media Kit
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.example.com/donate"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Donate
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="area-5"
|
|
||||||
>
|
|
||||||
<ul
|
|
||||||
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.facebook.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Facebook"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-facebook-square fa-2x"
|
|
||||||
id="edx-footer-icon-facebook"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Like example on Facebook
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.twitter.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Twitter"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-twitter-square fa-2x"
|
|
||||||
id="edx-footer-icon-twitter"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on Twitter
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.youtube.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Youtube"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-youtube-square fa-2x"
|
|
||||||
id="edx-footer-icon-youtube"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Subscribe to the example YouTube channel
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://www.linkedin.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="LinkedIn"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-linkedin-square fa-2x"
|
|
||||||
id="edx-footer-icon-linkedin"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on LinkedIn
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://plus.google.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Google+"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-google-plus-square fa-2x"
|
|
||||||
id="edx-footer-icon-google"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Follow example on Google+
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://reddit.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
title="Reddit"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden={true}
|
|
||||||
className="fa fa-reddit-square fa-2x"
|
|
||||||
id="edx-footer-icon-reddit"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="sr-only"
|
|
||||||
>
|
|
||||||
Subscribe to the example subreddit
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul
|
|
||||||
className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5"
|
|
||||||
>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://store.apple.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Download the example mobile app from the Apple App Store"
|
|
||||||
className="max-height-39"
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href="https://play.google.com"
|
|
||||||
onClick={[Function]}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="Download the example mobile app from Google Play"
|
|
||||||
className="max-height-39"
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<span>
|
|
||||||
© 2012–2019 example Inc.
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<span>
|
|
||||||
EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | 粤ICP备17044299号-2
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
`;
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
||||||
import { Hyperlink, Icon } from '@edx/paragon';
|
|
||||||
import messages from './SiteFooter.messages';
|
|
||||||
|
|
||||||
const EVENT_NAMES = {
|
|
||||||
FOOTER_LINK: 'edx.bi.footer.link',
|
|
||||||
};
|
|
||||||
|
|
||||||
class SiteFooter extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.externalLinkClickHandler = this.externalLinkClickHandler.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
externalLinkClickHandler(event) {
|
|
||||||
const label = event.currentTarget.getAttribute('href');
|
|
||||||
const eventName = EVENT_NAMES.FOOTER_LINK;
|
|
||||||
const properties = {
|
|
||||||
category: 'outbound_link',
|
|
||||||
label,
|
|
||||||
};
|
|
||||||
this.props.handleAllTrackEvents(eventName, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSiteLogo() {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src={this.props.siteLogo}
|
|
||||||
alt={this.props.intl.formatMessage(
|
|
||||||
messages['footer.site-footer.site-logo.alt-text'],
|
|
||||||
{ siteName: this.props.siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMarketingSiteUrl(path) {
|
|
||||||
return `${this.props.marketingSiteBaseUrl}${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSocialLinks() {
|
|
||||||
const {
|
|
||||||
intl,
|
|
||||||
siteName,
|
|
||||||
showSocialLinks,
|
|
||||||
facebookUrl,
|
|
||||||
twitterUrl,
|
|
||||||
youTubeUrl,
|
|
||||||
linkedInUrl,
|
|
||||||
googlePlusUrl,
|
|
||||||
redditUrl,
|
|
||||||
} = this.props;
|
|
||||||
let socialLinks = null;
|
|
||||||
if (showSocialLinks) {
|
|
||||||
socialLinks = (
|
|
||||||
<ul
|
|
||||||
className="d-flex flex-row justify-content-between list-unstyled max-width-222 p-0 mb-4"
|
|
||||||
>
|
|
||||||
{/* TODO: Use Paragon HyperLink with Icon. */}
|
|
||||||
{/* Would need to add rel to paragon if we still need it. */}
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={facebookUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.facebook.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-facebook"
|
|
||||||
className={['fa', 'fa-facebook-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.facebook.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={twitterUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.twitter.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-twitter"
|
|
||||||
className={['fa', 'fa-twitter-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.twitter.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={youTubeUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.youtube.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-youtube"
|
|
||||||
className={['fa', 'fa-youtube-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.youtube.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={linkedInUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.linkedin.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-linkedin"
|
|
||||||
className={['fa', 'fa-linkedin-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.linkedin.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={googlePlusUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.google-plus.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-google"
|
|
||||||
className={['fa', 'fa-google-plus-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.google-plus.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
href={redditUrl}
|
|
||||||
title={intl.formatMessage(messages['footer.site-footer.reddit.title'])}
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
onClick={this.externalLinkClickHandler}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
id="edx-footer-icon-reddit"
|
|
||||||
className={['fa', 'fa-reddit-square', 'fa-2x']}
|
|
||||||
screenReaderText={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.reddit.screen-reader-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return socialLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMobileLinks() {
|
|
||||||
const {
|
|
||||||
intl,
|
|
||||||
siteName,
|
|
||||||
showMobileLinks,
|
|
||||||
appleAppStoreUrl,
|
|
||||||
googlePlayUrl,
|
|
||||||
} = this.props;
|
|
||||||
let mobileLinks = null;
|
|
||||||
if (showMobileLinks) {
|
|
||||||
mobileLinks = (
|
|
||||||
<ul className="d-flex flex-row justify-content-between list-unstyled max-width-264 p-0 mb-5">
|
|
||||||
<li>
|
|
||||||
<a href={appleAppStoreUrl} rel="noopener noreferrer" target="_blank" onClick={this.externalLinkClickHandler}>
|
|
||||||
<img
|
|
||||||
className="max-height-39"
|
|
||||||
alt={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.apple-app-store.alt-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/app_store_badge_135x40.d0558d910630.svg"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={googlePlayUrl} rel="noopener noreferrer" target="_blank" onClick={this.externalLinkClickHandler}>
|
|
||||||
<img
|
|
||||||
className="max-height-39"
|
|
||||||
alt={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.google-play.alt-text'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
src="https://prod-edxapp.edx-cdn.org/static/images/app/google_play_badge_45.6ea466e328da.png"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return mobileLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
intl,
|
|
||||||
siteName,
|
|
||||||
openSourceUrl,
|
|
||||||
termsOfServiceUrl,
|
|
||||||
privacyPolicyUrl,
|
|
||||||
contactUrl,
|
|
||||||
supportUrl,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<footer
|
|
||||||
role="contentinfo"
|
|
||||||
aria-label={intl.formatMessage(messages['footer.site-footer.footer.aria-label'])}
|
|
||||||
className="footer d-flex justify-content-center border-top py-3 px-4"
|
|
||||||
>
|
|
||||||
<div className="max-width-1180 d-grid">
|
|
||||||
<div className="area-1">
|
|
||||||
<Hyperlink
|
|
||||||
destination={this.renderMarketingSiteUrl('/')}
|
|
||||||
content={this.renderSiteLogo()}
|
|
||||||
aria-label={intl.formatMessage(
|
|
||||||
messages['footer.site-footer.site-logo.aria-label'],
|
|
||||||
{ siteName },
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="area-2">
|
|
||||||
<h2>{siteName}</h2>
|
|
||||||
<ul className="list-unstyled p-0 m-0">
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/about-us')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.about"
|
|
||||||
defaultMessage="About"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/enterprise')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.business"
|
|
||||||
defaultMessage="{siteName} for Business"
|
|
||||||
values={{ siteName }}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/affiliate-program')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.affiliates"
|
|
||||||
defaultMessage="Affiliates"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={openSourceUrl}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.open-source"
|
|
||||||
defaultMessage="Open {siteName}"
|
|
||||||
values={{ siteName }}
|
|
||||||
description="Open Source link text"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/careers')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.careers"
|
|
||||||
defaultMessage="Careers"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/news-announcements')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.news"
|
|
||||||
defaultMessage="News"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="area-3">
|
|
||||||
<h2>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.header.legal"
|
|
||||||
defaultMessage="Legal"
|
|
||||||
description="Header for legal links"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
<ul className="list-unstyled p-0 m-0">
|
|
||||||
<li>
|
|
||||||
<a href={termsOfServiceUrl}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.terms-of-service"
|
|
||||||
defaultMessage="Terms of Service & Honor Code"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={privacyPolicyUrl}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.privacy"
|
|
||||||
defaultMessage="Privacy Policy"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/accessibility')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.accessibility"
|
|
||||||
defaultMessage="Accessibility Policy"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/trademarks')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.trademark"
|
|
||||||
defaultMessage="Trademark Policy"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/sitemap')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.sitemap"
|
|
||||||
defaultMessage="Sitemap"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="area-4">
|
|
||||||
<h2>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.header.connect"
|
|
||||||
defaultMessage="Connect"
|
|
||||||
description="Header for connect links"
|
|
||||||
/>
|
|
||||||
</h2>
|
|
||||||
<ul className="list-unstyled p-0 m-0">
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/blog')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.blog"
|
|
||||||
defaultMessage="Blog"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={contactUrl}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.contact-us"
|
|
||||||
defaultMessage="Contact Us"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={supportUrl}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.help-center"
|
|
||||||
defaultMessage="Help Center"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/media-kit')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.media-kit"
|
|
||||||
defaultMessage="Media Kit"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href={this.renderMarketingSiteUrl('/donate')}>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.link.donate"
|
|
||||||
defaultMessage="Donate"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className="area-5">
|
|
||||||
{this.renderSocialLinks()}
|
|
||||||
{this.renderMobileLinks()}
|
|
||||||
<p>
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.copyright-text"
|
|
||||||
defaultMessage="{copyrightSymbol} {startDate}–{endDate} {siteName} Inc."
|
|
||||||
values={{
|
|
||||||
copyrightSymbol: '©',
|
|
||||||
startDate: '2012',
|
|
||||||
endDate: `${new Date().getFullYear()}`,
|
|
||||||
siteName,
|
|
||||||
}}
|
|
||||||
description="Footer copyright text with copyright symbol and dates"
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<FormattedMessage
|
|
||||||
id="footer.site-footer.trademark-text"
|
|
||||||
defaultMessage="EdX, Open edX, and MicroMasters are registered trademarks of edX Inc. | {icpLicense}"
|
|
||||||
values={{ icpLicense: '粤ICP备17044299号-2' }}
|
|
||||||
description="Footer trademark text"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SiteFooter.propTypes = {
|
|
||||||
siteName: PropTypes.string,
|
|
||||||
siteLogo: PropTypes.node,
|
|
||||||
marketingSiteBaseUrl: PropTypes.string,
|
|
||||||
supportUrl: PropTypes.string,
|
|
||||||
contactUrl: PropTypes.string,
|
|
||||||
openSourceUrl: PropTypes.string,
|
|
||||||
termsOfServiceUrl: PropTypes.string,
|
|
||||||
privacyPolicyUrl: PropTypes.string,
|
|
||||||
showSocialLinks: PropTypes.bool,
|
|
||||||
facebookUrl: PropTypes.string,
|
|
||||||
twitterUrl: PropTypes.string,
|
|
||||||
youTubeUrl: PropTypes.string,
|
|
||||||
linkedInUrl: PropTypes.string,
|
|
||||||
googlePlusUrl: PropTypes.string,
|
|
||||||
redditUrl: PropTypes.string,
|
|
||||||
showMobileLinks: PropTypes.bool,
|
|
||||||
appleAppStoreUrl: PropTypes.string,
|
|
||||||
googlePlayUrl: PropTypes.string,
|
|
||||||
handleAllTrackEvents: PropTypes.func.isRequired,
|
|
||||||
intl: intlShape.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
SiteFooter.defaultProps = {
|
|
||||||
siteName: null,
|
|
||||||
siteLogo: null,
|
|
||||||
marketingSiteBaseUrl: null,
|
|
||||||
supportUrl: null,
|
|
||||||
contactUrl: null,
|
|
||||||
openSourceUrl: null,
|
|
||||||
termsOfServiceUrl: null,
|
|
||||||
privacyPolicyUrl: null,
|
|
||||||
showSocialLinks: true,
|
|
||||||
facebookUrl: null,
|
|
||||||
twitterUrl: null,
|
|
||||||
youTubeUrl: null,
|
|
||||||
linkedInUrl: null,
|
|
||||||
googlePlusUrl: null,
|
|
||||||
redditUrl: null,
|
|
||||||
showMobileLinks: true,
|
|
||||||
appleAppStoreUrl: null,
|
|
||||||
googlePlayUrl: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default injectIntl(SiteFooter);
|
|
||||||
export { EVENT_NAMES };
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import SiteFooter from './components/SiteFooter';
|
|
||||||
|
|
||||||
export default SiteFooter;
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
.max-width-222 {
|
|
||||||
max-width: 222px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-width-264 {
|
|
||||||
max-width: 264px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-width-1180 {
|
|
||||||
max-width: 1180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-height-39 {
|
|
||||||
max-height: 39px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.d-grid {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
$gray-footer: #fcfcfc;
|
|
||||||
$border-1: 1px solid $gray-200;
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
background-color: $gray-footer;
|
|
||||||
|
|
||||||
.area-1 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1;
|
|
||||||
border-bottom: $border-1;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-2 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 2;
|
|
||||||
border-bottom: $border-1;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-3 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 3;
|
|
||||||
border-bottom: $border-1;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-4 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 4;
|
|
||||||
border-bottom: $border-1;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-5 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 5;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 717px) {
|
|
||||||
.area-1 {
|
|
||||||
grid-column: 1 / span 2;
|
|
||||||
grid-row: 1;
|
|
||||||
border-bottom: none;
|
|
||||||
padding: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-2 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-3 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-4 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 4;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-5 {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 2 / span 3;
|
|
||||||
border-left: $border-1;
|
|
||||||
padding-left: 1rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 870px) {
|
|
||||||
.area-1 {
|
|
||||||
grid-column: 1;
|
|
||||||
grid-row: 1 / span 3;
|
|
||||||
border-right: $border-1;
|
|
||||||
padding-right: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-2 {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1;
|
|
||||||
border-bottom: none;
|
|
||||||
border-right: $border-1;
|
|
||||||
padding-right: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-3 {
|
|
||||||
grid-column: 3;
|
|
||||||
grid-row: 1;
|
|
||||||
border-bottom: none;
|
|
||||||
border-right: $border-1;
|
|
||||||
padding-right: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-4 {
|
|
||||||
grid-column: 4;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-5 {
|
|
||||||
grid-column: 2 / span 3;
|
|
||||||
grid-row: 2;
|
|
||||||
border: none;
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width: 1188px) {
|
|
||||||
.area-1 {
|
|
||||||
grid-column: 1 / span 1;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-2 {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-3 {
|
|
||||||
grid-column: 3;
|
|
||||||
grid-row: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-4 {
|
|
||||||
grid-column: 4;
|
|
||||||
grid-row: 1;
|
|
||||||
border-right: $border-1;
|
|
||||||
padding-right: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.area-5 {
|
|
||||||
grid-column: 5 / span 1;
|
|
||||||
grid-row: 1;
|
|
||||||
max-width: 372px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
src/plugin-slots/FooterSlot/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# FooterSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.footer.v1`
|
||||||
|
|
||||||
|
### Slot ID Aliases
|
||||||
|
* `footer_slot`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to repace/modify/hide the entire footer.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The following `env.config.jsx` will replace the default footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
with a simple custom footer
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.footer.v1': {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
// Hide the default footer
|
||||||
|
op: PLUGIN_OPERATIONS.Hide,
|
||||||
|
widgetId: 'default_contents',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Insert a custom footer
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'custom_footer',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
RenderWidget: () => (
|
||||||
|
<h1 style={{textAlign: 'center'}}>🦶</h1>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
BIN
src/plugin-slots/FooterSlot/images/custom_footer.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/plugin-slots/FooterSlot/images/default_footer.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
14
src/plugin-slots/FooterSlot/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import Footer from '../../components/Footer';
|
||||||
|
|
||||||
|
const FooterSlot = () => (
|
||||||
|
<PluginSlot
|
||||||
|
id="org.openedx.frontend.layout.footer.v1"
|
||||||
|
idAliases={['footer_slot', 'footer_plugin_slot']}
|
||||||
|
>
|
||||||
|
<Footer />
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FooterSlot;
|
||||||
5
src/plugin-slots/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# `frontend-component-footer` Plugin Slots
|
||||||
|
|
||||||
|
* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
|
||||||
|
* [`org.openedx.frontend.layout.studio_footer.v1`](./StudioFooterSlot/)
|
||||||
|
* [`org.openedx.frontend.layout.studio_footer_logo.v1`](./StudioFooterLogoSlot/)
|
||||||
47
src/plugin-slots/StudioFooterHelpButtonSlot/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# StudioFooterLogoSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.studio_footer_help_button.v1`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to repace/modify/hide the help button to the studio footer.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Add your custom button.
|
||||||
|
|
||||||
|
The following `env.config.jsx` will add a custom help button to the studio footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.studio_footer_help_button.v1': {
|
||||||
|
keepDefault: false,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'studio_footer_helpbutton_addition',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
priority: 40,
|
||||||
|
RenderWidget: ({isOpen, setIsOpen}) => {
|
||||||
|
return (
|
||||||
|
<Button className="button button1" onClick={() => setIsOpen(!isOpen)}>Custom Help Button</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 148 KiB |
17
src/plugin-slots/StudioFooterHelpButtonSlot/index.jsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import HelpButton from '../../components/studio-footer/help-components/HelpButton';
|
||||||
|
|
||||||
|
const StudioFooterHelpButtonSlot = ({ isOpen, setIsOpen }) => (
|
||||||
|
<PluginSlot id="org.openedx.frontend.layout.studio_footer_help_button.v1" pluginProps={{ isOpen, setIsOpen }}>
|
||||||
|
<HelpButton isOpen={isOpen} setIsOpen={setIsOpen} />
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
StudioFooterHelpButtonSlot.propTypes = {
|
||||||
|
setIsOpen: PropTypes.func.isRequired,
|
||||||
|
isOpen: PropTypes.bool.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudioFooterHelpButtonSlot;
|
||||||
50
src/plugin-slots/StudioFooterHelpContentSlot/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# StudioFooterLogoSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.studio_footer_help-content.v1`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to repace/modify/hide the help content to the studio footer.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Add a custom help content.
|
||||||
|
|
||||||
|
The following `env.config.jsx` will add a custom help content to the studio footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||||
|
import {
|
||||||
|
Hyperlink,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.studio_footer_help-content.v1': {
|
||||||
|
keepDefault: false,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'studio_footer_helpcontent_addition',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
priority: 40,
|
||||||
|
RenderWidget: () => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: '20px 5px' }}>
|
||||||
|
<p>Custom Help content</p>
|
||||||
|
<Hyperlink destination="https://example.com/">more help</Hyperlink>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 129 KiB |
13
src/plugin-slots/StudioFooterHelpContentSlot/index.jsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import HelpContent from '../../components/studio-footer/help-components/HelpContent';
|
||||||
|
|
||||||
|
const StudioFooterHelpContentSlot = () => (
|
||||||
|
<PluginSlot
|
||||||
|
id="org.openedx.frontend.layout.studio_footer_help-content.v1"
|
||||||
|
>
|
||||||
|
<HelpContent />
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StudioFooterHelpContentSlot;
|
||||||
53
src/plugin-slots/StudioFooterHelpSectionSlot/README.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# StudioFooterLogoSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.studio_footer_help_section.v1`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to repace/modify/hide the help section to the studio footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Customize help section.
|
||||||
|
|
||||||
|
The following `env.config.jsx` will add a custom help section to the studio footer.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||||
|
import {
|
||||||
|
Hyperlink,
|
||||||
|
Button,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.studio_footer_help_section.v1': {
|
||||||
|
keepDefault: false,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'studio_footer_helpsection_addition',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
priority: 40,
|
||||||
|
RenderWidget: () => {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', padding: '20px 5px' }}>
|
||||||
|
<p>Custom Help Section custizable as needed </p>
|
||||||
|
<Button className="button button1">Go to home</Button>
|
||||||
|
<Hyperlink destination="https://example.com/">About</Hyperlink>
|
||||||
|
<Hyperlink destination="https://example.com/">Info</Hyperlink>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 142 KiB |
19
src/plugin-slots/StudioFooterHelpSectionSlot/index.jsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
import HelpSection from '../../components/studio-footer/help-components/HelpSection';
|
||||||
|
|
||||||
|
const StudioFooterHelpSectionSlot = ({ containerProps }) => (
|
||||||
|
<PluginSlot id="org.openedx.frontend.layout.studio_footer_help_section.v1">
|
||||||
|
<HelpSection containerProps={containerProps} />
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
StudioFooterHelpSectionSlot.propTypes = {
|
||||||
|
containerProps: PropTypes.shape(Container.propTypes),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudioFooterHelpSectionSlot;
|
||||||
57
src/plugin-slots/StudioFooterLogoSlot/README.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# StudioFooterLogoSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.studio_footer_logo.v1`
|
||||||
|
|
||||||
|
### Slot ID Aliases
|
||||||
|
* `studio_footer_logo_slot`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to add your site logo to the studio footer.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Add your site logo.
|
||||||
|
|
||||||
|
The following `env.config.jsx` will add your site logo to the studio footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { PLUGIN_OPERATIONS, DIRECT_PLUGIN } from '@openedx/frontend-plugin-framework';
|
||||||
|
import {
|
||||||
|
Hyperlink,
|
||||||
|
Image,
|
||||||
|
} from '@openedx/paragon';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.studio_footer_logo.v1': {
|
||||||
|
keepDefault: true,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'studio_footer_logo_addition',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
priority: 40,
|
||||||
|
RenderWidget: () => {
|
||||||
|
return (
|
||||||
|
<Hyperlink destination="https://example.com/" className="float-right">
|
||||||
|
<Image
|
||||||
|
height="48px"
|
||||||
|
alt="Hosted by MySite"
|
||||||
|
src="https://logos.openedx.org/generic-logo.svg"
|
||||||
|
/>
|
||||||
|
</Hyperlink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
After Width: | Height: | Size: 285 KiB |
10
src/plugin-slots/StudioFooterLogoSlot/index.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
|
||||||
|
const StudioFooterLogoSlot = () => (
|
||||||
|
<PluginSlot id="org.openedx.frontend.layout.studio_footer_logo.v1" idAliases={['studio_footer_logo_slot']}>
|
||||||
|
{/* Andal Learning branding - Open edX logo removed */}
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StudioFooterLogoSlot;
|
||||||
51
src/plugin-slots/StudioFooterSlot/README.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# StudioFooterSlot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.layout.studio_footer.v1`
|
||||||
|
|
||||||
|
### Slot ID Aliases
|
||||||
|
* `studio_footer_slot`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to repace/modify/hide the entire studio footer.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
The following `env.config.jsx` will replace the default studio footer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
with a simple custom footer
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```jsx
|
||||||
|
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.layout.studio_footer.v1': {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
// Hide the default footer
|
||||||
|
op: PLUGIN_OPERATIONS.Hide,
|
||||||
|
widgetId: 'default_contents',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Insert a custom footer
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'custom_footer',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
RenderWidget: () => (
|
||||||
|
<h1 style={{textAlign: 'center'}}>🦶</h1>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
BIN
src/plugin-slots/StudioFooterSlot/images/custom_footer.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 79 KiB |
11
src/plugin-slots/StudioFooterSlot/index.jsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import StudioFooter from '../../components/studio-footer';
|
||||||
|
|
||||||
|
const StudioFooterSlot = () => (
|
||||||
|
<PluginSlot id="org.openedx.frontend.layout.studio_footer.v1" idAliases={['studio_footer_slot']}>
|
||||||
|
<StudioFooter />
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StudioFooterSlot;
|
||||||
22
src/setupTest.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// These configuration values are usually set in webpack's EnvironmentPlugin however
|
||||||
|
// Jest does not use webpack so we need to set these so for testing
|
||||||
|
process.env.ACCESS_TOKEN_COOKIE_NAME = 'edx-jwt-cookie-header-payload';
|
||||||
|
process.env.BASE_URL = 'localhost:1995';
|
||||||
|
process.env.CREDENTIALS_BASE_URL = 'http://localhost:18150';
|
||||||
|
process.env.CSRF_TOKEN_API_PATH = '/csrf/api/v1/token';
|
||||||
|
process.env.ECOMMERCE_BASE_URL = 'http://localhost:18130';
|
||||||
|
process.env.LANGUAGE_PREFERENCE_COOKIE_NAME = 'openedx-language-preference';
|
||||||
|
process.env.LMS_BASE_URL = 'http://localhost:18000';
|
||||||
|
process.env.LOGIN_URL = 'http://localhost:18000/login';
|
||||||
|
process.env.LOGOUT_URL = 'http://localhost:18000/login';
|
||||||
|
process.env.MARKETING_SITE_BASE_URL = 'http://localhost:18000';
|
||||||
|
process.env.ORDER_HISTORY_URL = 'localhost:1996/orders';
|
||||||
|
process.env.REFRESH_ACCESS_TOKEN_ENDPOINT = 'http://localhost:18000/login_refresh';
|
||||||
|
process.env.SEGMENT_KEY = 'segment_whoa';
|
||||||
|
process.env.SITE_NAME = 'edX';
|
||||||
|
process.env.USER_INFO_COOKIE_NAME = 'edx-user-info';
|
||||||
|
process.env.LOGO_URL = 'https://edx-cdn.org/v3/default/logo.svg';
|
||||||
|
process.env.LOGO_TRADEMARK_URL = 'https://edx-cdn.org/v3/default/logo-trademark.svg';
|
||||||
|
process.env.LOGO_WHITE_URL = 'https://edx-cdn.org/v3/default/logo-white.svg';
|
||||||
|
process.env.FAVICON_URL = 'https://edx-cdn.org/v3/default/favicon.ico';
|
||||||
|
process.env.ENABLE_ACCESSIBILITY_PAGE = 'false';
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import Enzyme from 'enzyme';
|
|
||||||
import Adapter from 'enzyme-adapter-react-16';
|
|
||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
|
||||||
17
webpack.dev.config.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const { createConfig } = require('@openedx/frontend-build');
|
||||||
|
|
||||||
|
module.exports = createConfig('webpack-dev', {
|
||||||
|
entry: {
|
||||||
|
app: path.resolve(__dirname, 'example'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'example/dist'),
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@edx/frontend-component-footer': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||