Compare commits

..

29 Commits

Author SHA1 Message Date
Chris Deery
6c9899be5e feat: [AA-906] A11y changes for buttons to be seen as radio buttons 2021-10-01 14:35:20 -04:00
Chris Deery
93873cd845 feat: [AA-906] A11y changes for buttons to be seen as radio buttons 2021-10-01 14:22:58 -04:00
Chris Deery
3def37d28c feat: [AA-906] Merge conflicts with Master 2021-09-30 13:48:10 -04:00
Chris Deery
d7e7b8c873 feat: [AA-906] Additional tests 2021-09-30 11:56:40 -04:00
Chris Deery
b401f7bb34 feat: [AA-906] missed one review comment 2021-09-30 11:56:40 -04:00
Chris Deery
952d5d89e7 feat: [AA-906] Cleanup and add some Unit tests 2021-09-30 11:56:40 -04:00
Chris Deery
15d9c9f1bd feat: [AA-906] rebase from master 2021-09-30 11:56:19 -04:00
Chris Deery
b5e22c983d feat: [AA-906] update snapshot 2021-09-30 11:52:55 -04:00
Chris Deery
4d3b68d287 feat: [AA-906] implemented review feedback 2021-09-30 11:52:55 -04:00
Chris Deery
2607cec626 feat: [AA-906] updated redux.test.js.snap with new data for goals 2021-09-30 11:52:55 -04:00
Chris Deery
ec18af1ec0 feat: [AA-906] Front end for Number of Days goal setting
fix errors from AccountActivationAlert.jsx
2021-09-30 11:52:55 -04:00
Chris Deery
ee62aab0e7 feat: [AA-906] Front end for Number of Days goal setting
fix undefined access
2021-09-30 11:52:55 -04:00
Chris Deery
3cb55b320a feat: [AA-906] Front end for Number of Days goal setting
remove console.log calls
2021-09-30 11:52:55 -04:00
Chris Deery
d2e26bf8cf feat: [AA-906] Front end for Number of Days goal setting
fix improper I18n messages
2021-09-30 11:52:55 -04:00
Chris Deery
95fb4ec859 feat: [AA-906] Front end for Number of Days goal setting
Implement days per week buttons
link buttons to api to set value
Implement subscribe button
link subscribe button to api
tweak CSS
Add new icons for flags
Add new function for updating weekly goals
2021-09-30 11:52:55 -04:00
Chris Deery
03f0ec4aec feat: [AA-906] Front end for Number of Days goal setting
Consume flag for course_goals[number_of_days_goals_enabled]
Added StartResumeCard to outlineTab
Move the Start/Resume course button to above the Intro
Put the start/resume in a card with a text block
2021-09-30 11:52:55 -04:00
Chris Deery
9dc447fa85 feat: [AA-906] Additional tests 2021-09-29 09:41:50 -04:00
Chris Deery
0286304c39 feat: [AA-906] missed one review comment 2021-09-27 09:02:15 -04:00
Chris Deery
d88c4ade78 feat: [AA-906] Cleanup and add some Unit tests 2021-09-27 08:43:09 -04:00
Chris Deery
e3b8143677 feat: [AA-906] more review updates and refactoring 2021-09-21 17:29:10 -04:00
Chris Deery
09384b317e feat: [AA-906] update snapshot 2021-09-21 15:15:03 -04:00
Chris Deery
2172ea4041 feat: [AA-906] implemented review feedback 2021-09-21 15:02:26 -04:00
Chris Deery
7e3aee7147 feat: [AA-906] updated redux.test.js.snap with new data for goals 2021-09-20 15:29:38 -04:00
Chris Deery
4f187390a0 feat: [AA-906] Front end for Number of Days goal setting
fix errors from AccountActivationAlert.jsx
2021-09-20 15:10:44 -04:00
Chris Deery
5b3053d3bb feat: [AA-906] Front end for Number of Days goal setting
fix undefined access
2021-09-17 17:04:30 -04:00
Chris Deery
dae1e03e73 feat: [AA-906] Front end for Number of Days goal setting
remove console.log calls
2021-09-17 12:03:51 -04:00
Chris Deery
a32c79984d feat: [AA-906] Front end for Number of Days goal setting
fix improper I18n messages
2021-09-17 11:51:34 -04:00
Chris Deery
8d0596a32e feat: [AA-906] Front end for Number of Days goal setting
Implement days per week buttons
link buttons to api to set value
Implement subscribe button
link subscribe button to api
tweak CSS
Add new icons for flags
Add new function for updating weekly goals
2021-09-17 11:40:12 -04:00
Chris Deery
91b1229a29 feat: [AA-906] Front end for Number of Days goal setting
Consume flag for course_goals[number_of_days_goals_enabled]
Added StartResumeCard to outlineTab
Move the Start/Resume course button to above the Intro
Put the start/resume in a card with a text block
2021-09-09 09:56:31 -04:00
41 changed files with 718 additions and 707 deletions

2
.env
View File

@@ -37,4 +37,4 @@ TWITTER_HASHTAG=''
TWITTER_URL=''
USER_INFO_COOKIE_NAME=''
SESSION_COOKIE_DOMAIN=''
ENABLE_NOTICES=''
ENABLE_JUMPNAV='true'

View File

@@ -37,4 +37,4 @@ TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
SESSION_COOKIE_DOMAIN='localhost'
ENABLE_NOTICES=''
ENABLE_JUMPNAV='true'

View File

@@ -36,4 +36,4 @@ TERMS_OF_SERVICE_URL='https://www.edx.org/edx-terms-service'
TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info'
ENABLE_NOTICES=''
ENABLE_JUMPNAV='true'

View File

@@ -1,10 +0,0 @@
# Run commitlint on the commit messages in a pull request.
name: Lint Commit Messages
on:
- pull_request
jobs:
commitlint:
uses: edx/.github/.github/workflows/commitlint.yml@master

View File

@@ -109,3 +109,9 @@ TWITTER_URL
unless this is set. Optional.
Example: https://twitter.com/edXOnline
ENABLE_JUMPNAV
Enables the new Jump Navigation feature in the course breadcrumbs, defaulted to the string 'true'.
Disable to have simple hyperlinks for breadcrumbs. Setting it to any other value but 'true' ('false','I love flags', 'etc' would disable the Jumpnav).
This feature flag is slated to be removed as jumpnav becomes default. Follow the progress of this ticket here:
https://openedx.atlassian.net/browse/TNL-8678

252
package-lock.json generated
View File

@@ -3663,9 +3663,9 @@
}
},
"@edx/frontend-lib-special-exams": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-1.13.3.tgz",
"integrity": "sha512-WTjOPh/Rr6wAKImRdmJaO4g2u+Q0pxw/FLzS7PJ0cE63hoMM8VsVL6diS1Alg01mG5DYXuiX8QyHb9lk0pJzdw==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-1.13.2.tgz",
"integrity": "sha512-NzMgyVAg63x6vg0vSe00MhREyeRSyXE16aBt/34XQHXQKG0ieEVnu3o3oQdlgzu2JWzGxxVmZr4gGeYB3qA7hQ==",
"requires": {
"@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.11.2",
@@ -4499,11 +4499,11 @@
}
},
"@pact-foundation/pact": {
"version": "9.16.3",
"resolved": "https://registry.npmjs.org/@pact-foundation/pact/-/pact-9.16.3.tgz",
"integrity": "sha512-26uhezEbBAbW7gQ0V2QTJp8iSTmgxf9A65J7lCQyTd6P3Sk7omqTL4faio1/UnPlzMaogQjaa8OpmFpSbfXOFg==",
"version": "9.16.1",
"resolved": "https://registry.npmjs.org/@pact-foundation/pact/-/pact-9.16.1.tgz",
"integrity": "sha512-hknXmKy3uvZsJ2rJlazyUk1hJWnxRuxAFbMHZ/edWjxF2gmQO3xyA7SKFjgEhbghORcgLnK3308q/5rJFOfbQg==",
"requires": {
"@pact-foundation/pact-node": "^10.13.8",
"@pact-foundation/pact-node": "^10.13.7",
"@types/bluebird": "^3.5.20",
"@types/express": "^4.17.11",
"bluebird": "~3.5.1",
@@ -4515,6 +4515,7 @@
"graphql": "^14.0.0",
"graphql-tag": "^2.9.1",
"http-proxy": "^1.18.1",
"http-proxy-middleware": "^0.19.0",
"lodash": "^4.17.21",
"lodash.isfunction": "3.0.8",
"lodash.isnil": "4.0.0",
@@ -4535,9 +4536,9 @@
}
},
"@pact-foundation/pact-node": {
"version": "10.13.8",
"resolved": "https://registry.npmjs.org/@pact-foundation/pact-node/-/pact-node-10.13.8.tgz",
"integrity": "sha512-0Jo9b2UNB/qEcsn+fT3+7M68x6DX5lYq82zFIczgbNxVIbuCGkR+xO7u6LnKuO1eyrGfT2WwP7LVLHEJSfMxTw==",
"version": "10.13.7",
"resolved": "https://registry.npmjs.org/@pact-foundation/pact-node/-/pact-node-10.13.7.tgz",
"integrity": "sha512-EhSo5t0QCW5CXdqXPtLo/tkAmAn0Phm7qNgPibh5p5+38Mdrjee77Muk1LVd/MjlW6NV5dH+zLAlD40z4CRelw==",
"requires": {
"@types/needle": "^2.5.1",
"@types/pino": "^6.3.5",
@@ -4669,11 +4670,11 @@
"integrity": "sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw=="
},
"@reduxjs/toolkit": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.2.tgz",
"integrity": "sha512-HbfI/hOVrAcMGAYsMWxw3UJyIoAS9JTdwddsjlr5w3S50tXhWb+EMyhIw+IAvCVCLETkzdjgH91RjDSYZekVBA==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.1.tgz",
"integrity": "sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==",
"requires": {
"immer": "^9.0.6",
"immer": "^9.0.1",
"redux": "^4.1.0",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0"
@@ -6749,20 +6750,17 @@
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
"dev": true
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
},
"arr-flatten": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
"dev": true
"integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg=="
},
"arr-union": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
"dev": true
"integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ="
},
"array-find-index": {
"version": "1.0.2",
@@ -6807,8 +6805,7 @@
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
"dev": true
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
},
"array.prototype.find": {
"version": "2.1.1",
@@ -6838,8 +6835,7 @@
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
"dev": true
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
"ast-types-flow": {
"version": "0.0.7",
@@ -6888,8 +6884,7 @@
"atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"dev": true
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"atomic-sleep": {
"version": "1.0.0",
@@ -7279,7 +7274,6 @@
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
"integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
"dev": true,
"requires": {
"cache-base": "^1.0.1",
"class-utils": "^0.3.5",
@@ -7294,7 +7288,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"requires": {
"is-descriptor": "^1.0.0"
}
@@ -7303,7 +7296,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -7312,7 +7304,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -7321,7 +7312,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
@@ -7356,9 +7346,9 @@
}
},
"big-integer": {
"version": "1.6.49",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.49.tgz",
"integrity": "sha512-KJ7VhqH+f/BOt9a3yMwJNmcZjG53ijWMTjSAGMveQWyLwqIiwkjNP5PFgDob3Snnx86SjDj6I89fIbv0dkQeNw=="
"version": "1.6.48",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz",
"integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w=="
},
"big.js": {
"version": "5.2.2",
@@ -7847,7 +7837,6 @@
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
@@ -7865,7 +7854,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -7986,7 +7974,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
"integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
"dev": true,
"requires": {
"collection-visit": "^1.0.0",
"component-emitter": "^1.2.1",
@@ -8347,7 +8334,6 @@
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
"integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"define-property": "^0.2.5",
@@ -8359,7 +8345,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
"is-descriptor": "^0.1.0"
}
@@ -8553,7 +8538,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
"integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
"dev": true,
"requires": {
"map-visit": "^1.0.0",
"object-visit": "^1.0.0"
@@ -8613,8 +8597,7 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"compressible": {
"version": "2.0.18",
@@ -8796,8 +8779,7 @@
"copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40="
},
"core-js": {
"version": "3.16.4",
@@ -9131,9 +9113,9 @@
}
},
"dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.5.1.tgz",
"integrity": "sha512-OD0TZ+B7yP7ZgpJf5K2DIbj3FZvFvxgFUuaqA/V5zTjAtAAXZ1E8bktHxmAGs4x5b7PflqA9LeQ84Og7wYtF7Q=="
},
"debug": {
"version": "2.6.9",
@@ -9460,7 +9442,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
"integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
"dev": true,
"requires": {
"is-descriptor": "^1.0.2",
"isobject": "^3.0.1"
@@ -9470,7 +9451,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -9479,7 +9459,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -9488,7 +9467,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
@@ -10689,7 +10667,6 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
"dev": true,
"requires": {
"debug": "^2.3.3",
"define-property": "^0.2.5",
@@ -10704,7 +10681,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
"is-descriptor": "^0.1.0"
}
@@ -10713,7 +10689,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -10820,9 +10795,9 @@
}
},
"ext": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz",
"integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.5.0.tgz",
"integrity": "sha512-+ONcYoWj/SoQwUofMr94aGu05Ou4FepKi7N7b+O8T4jVfyIsZQV1/xeS8jpaBzF0csAk0KLXoHCxU7cKYZjo1Q==",
"requires": {
"type": "^2.5.0"
},
@@ -10865,7 +10840,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
"dev": true,
"requires": {
"assign-symbols": "^1.0.0",
"is-extendable": "^1.0.1"
@@ -10875,7 +10849,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
"dev": true,
"requires": {
"is-plain-object": "^2.0.4"
}
@@ -10897,7 +10870,6 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
"dev": true,
"requires": {
"array-unique": "^0.3.2",
"define-property": "^1.0.0",
@@ -10913,7 +10885,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"requires": {
"is-descriptor": "^1.0.0"
}
@@ -10922,7 +10893,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -10931,7 +10901,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -10940,7 +10909,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -10949,7 +10917,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
@@ -11206,7 +11173,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
@@ -11218,7 +11184,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -11331,8 +11296,7 @@
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
"dev": true
"integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA="
},
"foreach": {
"version": "2.0.5",
@@ -11398,7 +11362,6 @@
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
"integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
"dev": true,
"requires": {
"map-cache": "^0.2.2"
}
@@ -11629,8 +11592,7 @@
"get-value": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
"dev": true
"integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg="
},
"gifsicle": {
"version": "5.2.0",
@@ -11986,7 +11948,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
"integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
"dev": true,
"requires": {
"get-value": "^2.0.6",
"has-values": "^1.0.0",
@@ -11997,7 +11958,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
"integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"kind-of": "^4.0.0"
@@ -12007,7 +11967,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
"integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -12276,6 +12235,17 @@
}
}
},
"http-proxy-middleware": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz",
"integrity": "sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==",
"requires": {
"http-proxy": "^1.18.1",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
}
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
@@ -12749,9 +12719,9 @@
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
},
"immer": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.6.tgz",
"integrity": "sha512-G95ivKpy+EvVAnAab4fVa4YGYn24J1SpEktnJX7JJ45Bd7xqME/SCplFzYFmTbrkwZbQ4xJK1xMTUYBkN6pWsQ=="
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.5.tgz",
"integrity": "sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ=="
},
"import-fresh": {
"version": "3.3.0",
@@ -13003,7 +12973,6 @@
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
@@ -13012,7 +12981,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -13113,7 +13081,6 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
@@ -13122,7 +13089,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -13138,7 +13104,6 @@
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^0.1.6",
"is-data-descriptor": "^0.1.4",
@@ -13148,8 +13113,7 @@
"kind-of": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
"dev": true
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
}
}
},
@@ -13162,14 +13126,12 @@
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
"dev": true
"integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-finite": {
"version": "1.1.0",
@@ -13221,7 +13183,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
@@ -13284,7 +13245,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
@@ -13293,7 +13253,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -13347,7 +13306,6 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
"dev": true,
"requires": {
"isobject": "^3.0.1"
}
@@ -13495,8 +13453,7 @@
"is-windows": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
"dev": true
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="
},
"is-wsl": {
"version": "2.2.0",
@@ -13520,8 +13477,7 @@
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"isstream": {
"version": "0.1.2",
@@ -16833,8 +16789,7 @@
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
},
"kleur": {
"version": "3.0.3",
@@ -17325,8 +17280,7 @@
"map-cache": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
"dev": true
"integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8="
},
"map-obj": {
"version": "1.0.1",
@@ -17339,7 +17293,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
"integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
"dev": true,
"requires": {
"object-visit": "^1.0.0"
}
@@ -17572,7 +17525,6 @@
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
@@ -17675,9 +17627,9 @@
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz",
"integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
"requires": {
"yallist": "^4.0.0"
}
@@ -17695,7 +17647,6 @@
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
"integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
"is-extendable": "^1.0.1"
@@ -17705,7 +17656,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
"dev": true,
"requires": {
"is-plain-object": "^2.0.4"
}
@@ -17781,7 +17731,6 @@
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
"integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
@@ -18040,7 +17989,6 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
"integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
"dev": true,
"requires": {
"copy-descriptor": "^0.1.0",
"define-property": "^0.2.5",
@@ -18051,7 +17999,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
"is-descriptor": "^0.1.0"
}
@@ -18060,7 +18007,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -18095,7 +18041,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
"integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
"dev": true,
"requires": {
"isobject": "^3.0.0"
}
@@ -18149,7 +18094,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
"integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
"dev": true,
"requires": {
"isobject": "^3.0.1"
}
@@ -18502,8 +18446,7 @@
"pascalcase": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
"dev": true
"integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ="
},
"path-dirname": {
"version": "1.0.2",
@@ -18589,9 +18532,9 @@
}
},
"pino": {
"version": "6.13.3",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.3.tgz",
"integrity": "sha512-tJy6qVgkh9MwNgqX1/oYi3ehfl2Y9H0uHyEEMsBe74KinESIjdMrMQDWpcZPpPicg3VV35d/GLQZmo4QgU2Xkg==",
"version": "6.13.2",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.2.tgz",
"integrity": "sha512-vmD/cabJ4xKqo9GVuAoAEeQhra8XJ7YydPV/JyIP+0zDtFTu5JSKdtt8eksGVWKtTSrNGcRrzJ4/IzvUWep3FA==",
"requires": {
"fast-redact": "^3.0.0",
"fast-safe-stringify": "^2.0.8",
@@ -18939,8 +18882,7 @@
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
"dev": true
"integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs="
},
"postcss": {
"version": "8.3.5",
@@ -19593,9 +19535,9 @@
"dev": true
},
"quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz",
"integrity": "sha512-MaL/oqh02mhEo5m5J2rwsVL23Iw2PEaGVHgT2vFt8AAsr0lfvQA5dpXo9TPu0rz7tSBdUPgkbam0j/fj5ZM8yg=="
},
"randombytes": {
"version": "2.1.0",
@@ -20288,7 +20230,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
"integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
"dev": true,
"requires": {
"extend-shallow": "^3.0.2",
"safe-regex": "^1.1.0"
@@ -20453,14 +20394,12 @@
"repeat-element": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
"integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
"dev": true
"integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ=="
},
"repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
"dev": true
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"repeating": {
"version": "2.0.1",
@@ -20541,8 +20480,7 @@
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo="
},
"resolve-url-loader": {
"version": "5.0.0-beta.1",
@@ -20580,8 +20518,7 @@
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"dev": true
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
},
"retry": {
"version": "0.13.1",
@@ -20751,7 +20688,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
"integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
"dev": true,
"requires": {
"ret": "~0.1.10"
}
@@ -21000,7 +20936,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
"integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-extendable": "^0.1.1",
@@ -21012,7 +20947,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -21122,7 +21056,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
"integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
"dev": true,
"requires": {
"base": "^0.11.1",
"debug": "^2.2.0",
@@ -21138,7 +21071,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
"is-descriptor": "^0.1.0"
}
@@ -21147,7 +21079,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
@@ -21155,8 +21086,7 @@
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
@@ -21164,7 +21094,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
"integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
"dev": true,
"requires": {
"define-property": "^1.0.0",
"isobject": "^3.0.0",
@@ -21175,7 +21104,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"dev": true,
"requires": {
"is-descriptor": "^1.0.0"
}
@@ -21184,7 +21112,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
"integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -21193,7 +21120,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
"integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
"dev": true,
"requires": {
"kind-of": "^6.0.0"
}
@@ -21202,7 +21128,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
"integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
"dev": true,
"requires": {
"is-accessor-descriptor": "^1.0.0",
"is-data-descriptor": "^1.0.0",
@@ -21215,7 +21140,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
"integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
"dev": true,
"requires": {
"kind-of": "^3.2.0"
},
@@ -21224,7 +21148,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -21243,9 +21166,9 @@
}
},
"sonic-boom": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.3.0.tgz",
"integrity": "sha512-lEPaw654/4/rCJHz/TNzV4GIthqCq4inO+O3aFhbdOvR1bE+2//sVkcS+xlqPdb8gdjQCEE0hE9BuvnVixbnWQ==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.2.3.tgz",
"integrity": "sha512-dm32bzlBchhXoJZe0yLY/kdYsHtXhZphidIcCzJib1aEjfciZyvHJ3NjA1zh6jJCO/OBLfdjc5iw6jLS/Go2fg==",
"requires": {
"atomic-sleep": "^1.0.0"
}
@@ -21324,7 +21247,6 @@
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
"integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
"dev": true,
"requires": {
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0",
@@ -21346,8 +21268,7 @@
"source-map-url": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
"dev": true
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
},
"spawn-sync": {
"version": "1.0.15",
@@ -21456,7 +21377,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
"integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
"dev": true,
"requires": {
"extend-shallow": "^3.0.0"
}
@@ -21573,7 +21493,6 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
"integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
"dev": true,
"requires": {
"define-property": "^0.2.5",
"object-copy": "^0.1.0"
@@ -21583,7 +21502,6 @@
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"dev": true,
"requires": {
"is-descriptor": "^0.1.0"
}
@@ -22546,7 +22464,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
"integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
@@ -22555,7 +22472,6 @@
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
@@ -22566,7 +22482,6 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
"integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
"dev": true,
"requires": {
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
@@ -22578,7 +22493,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
@@ -22805,7 +22719,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
"integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
@@ -22867,7 +22780,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
"integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
"dev": true,
"requires": {
"has-value": "^0.3.1",
"isobject": "^3.0.0"
@@ -22877,7 +22789,6 @@
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
"integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
"dev": true,
"requires": {
"get-value": "^2.0.3",
"has-values": "^0.1.4",
@@ -22888,7 +22799,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
"integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
"dev": true,
"requires": {
"isarray": "1.0.0"
}
@@ -22898,14 +22808,12 @@
"has-values": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
"integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
"dev": true
"integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
}
}
},
@@ -22983,8 +22891,7 @@
"urix": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
"dev": true
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
},
"url": {
"version": "0.11.0",
@@ -23049,8 +22956,7 @@
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
"use-callback-ref": {
"version": "1.2.5",

View File

@@ -34,7 +34,7 @@
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.1.6",
"@edx/frontend-enterprise-utils": "1.0.0",
"@edx/frontend-lib-special-exams": "1.13.3",
"@edx/frontend-lib-special-exams": "1.13.2",
"@edx/frontend-platform": "1.12.7",
"@edx/paragon": "16.13.3",
"@fortawesome/fontawesome-svg-core": "1.2.36",
@@ -42,8 +42,8 @@
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.15",
"@pact-foundation/pact": "9.16.3",
"@reduxjs/toolkit": "1.6.2",
"@pact-foundation/pact": "9.16.1",
"@reduxjs/toolkit": "1.6.1",
"classnames": "2.3.1",
"core-js": "3.16.4",
"js-cookie": "2.2.1",

View File

@@ -1,6 +1,11 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Course | <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<% if (process.env.NEW_RELIC_ACCOUNT_ID) { %>
<!-- NewRelic agent 1209 -->
<script>
@@ -11,11 +16,6 @@
</script>
<% } %>
<title>Course | <%= process.env.SITE_NAME %></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="<%=htmlWebpackPlugin.options.FAVICON_URL%>" type="image/x-icon" />
<% if (htmlWebpackPlugin.options.OPTIMIZELY_PROJECT_ID) { %>
<script src="https://www.edx.org/optimizelyjs/<%= htmlWebpackPlugin.options.OPTIMIZELY_PROJECT_ID %>.js"></script>
<% } %>

View File

@@ -9,10 +9,13 @@ import {
Icon,
} from '@edx/paragon';
import { Check, ArrowForward } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendActivationEmail } from '../../courseware/data';
import messages from './messages';
function AccountActivationAlert() {
function AccountActivationAlert({
intl,
}) {
const [showModal, setShowModal] = useState(false);
const [showSpinner, setShowSpinner] = useState(false);
const [showCheck, setShowCheck] = useState(false);
@@ -29,22 +32,12 @@ function AccountActivationAlert() {
if (showAccountActivationAlert !== undefined) {
Cookies.remove('show-account-activation-popup', { path: '/', domain: process.env.SESSION_COOKIE_DOMAIN });
// extra check to make sure cookie was removed before updating the state. Updating the state without removal
// of cookie would make it infinit rendering
// of cookie would make it infinite rendering
if (Cookies.get('show-account-activation-popup') === undefined) {
setShowModal(true);
}
}
const title = (
<h3>
<FormattedMessage
id="account-activation.alert.title"
defaultMessage="Activate your account so you can log back in"
description="Title for account activation alert which is shown after the registration"
/>
</h3>
);
const button = (
<Button
variant="primary"
@@ -64,7 +57,7 @@ function AccountActivationAlert() {
);
const children = () => {
let bodyContent = null;
let bodyContent;
const message = (
<FormattedMessage
id="account-activation.alert.message"
@@ -123,7 +116,7 @@ function AccountActivationAlert() {
return (
<AlertModal
isOpen={showModal}
title={title}
title={intl.formatMessage(messages.accountActivationAlertTitle)}
footerNode={button}
onClose={() => ({})}
>
@@ -132,4 +125,8 @@ function AccountActivationAlert() {
);
}
AccountActivationAlert.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(AccountActivationAlert);

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
accountActivationAlertTitle: {
id: 'account-activation.alert.title',
defaultMessage: 'Activate your account so you can log back in',
description: 'Title for account activation alert which is shown after the registration',
},
});
export default messages;

View File

@@ -40,6 +40,9 @@ Factory.define('outlineTabData')
course_goals: {
goal_options: [],
selected_goal: null,
number_of_days_goals_enabled: false,
days_per_week: null,
subscribed_to_reminders: null,
},
course_tools: [
{

View File

@@ -432,8 +432,11 @@ Object {
},
},
"courseGoals": Object {
"daysPerWeek": null,
"goalOptions": Array [],
"numberOfDaysGoalsEnabled": false,
"selectedGoal": null,
"subscribedToReminders": null,
},
"courseTools": Array [
Object {

View File

@@ -391,6 +391,15 @@ export async function postCourseGoals(courseId, goalKey) {
return getAuthenticatedHttpClient().post(url.href, { course_id: courseId, goal_key: goalKey });
}
export async function postWeeklyCourseGoals(courseId, daysPerWeek, subscribedToReminders) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
return getAuthenticatedHttpClient().post(url.href, {
course_id: courseId,
days_per_week: daysPerWeek,
subscribed_to_reminders: subscribedToReminders,
});
}
export async function postDismissWelcomeMessage(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/dismiss_welcome_message`);
await getAuthenticatedHttpClient().post(url.href, { course_id: courseId });

View File

@@ -4,6 +4,7 @@ export {
fetchProgressTab,
resetDeadlines,
saveCourseGoal,
saveWeeklyCourseGoal,
} from './thunks';
export { reducer } from './slice';

View File

@@ -8,6 +8,7 @@ import {
getProgressTabData,
postCourseDeadlines,
postCourseGoals,
postWeeklyCourseGoals,
postDismissWelcomeMessage,
postRequestCert,
} from './api';
@@ -113,6 +114,10 @@ export async function saveCourseGoal(courseId, goalKey) {
return postCourseGoals(courseId, goalKey);
}
export async function saveWeeklyCourseGoal(courseId, daysPerWeek, subscribedToReminders) {
return postWeeklyCourseGoals(courseId, daysPerWeek, subscribedToReminders);
}
export function processEvent(eventData, getTabData) {
return async (dispatch) => {
// Pulling this out early so the data doesn't get camelCased and is easier

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -10,6 +10,8 @@ import { AlertList } from '../../generic/user-messages';
import CourseDates from './widgets/CourseDates';
import CourseGoalCard from './widgets/CourseGoalCard';
import CourseHandouts from './widgets/CourseHandouts';
import StartOrResumeCourseCard from './widgets/StartOrResumeCourseCard';
import WeeklyLearningGoalCard from './widgets/WeeklyLearningGoalCard';
import CourseTools from './widgets/CourseTools';
import { fetchOutlineTab } from '../data';
import genericMessages from '../../generic/messages';
@@ -54,13 +56,13 @@ function OutlineTab({ intl }) {
courseGoals: {
goalOptions,
selectedGoal,
weeklyLearningGoalEnabled,
} = {},
datesBannerInfo,
datesWidget: {
courseDateBlocks,
},
resumeCourse: {
hasVisitedCourse,
url: resumeCourseUrl,
},
offer,
@@ -68,7 +70,7 @@ function OutlineTab({ intl }) {
verifiedMode,
} = useModel('outline', courseId);
const [courseGoalToDisplay, setCourseGoalToDisplay] = useState(selectedGoal);
const [deprecatedCourseGoalToDisplay, setDeprecatedCourseGoalToDisplay] = useState(selectedGoal);
const [goalToastHeader, setGoalToastHeader] = useState('');
const [expandAll, setExpandAll] = useState(false);
@@ -77,14 +79,6 @@ function OutlineTab({ intl }) {
courserun_key: courseId,
};
const logResumeCourseClick = () => {
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
...eventProperties,
event_type: hasVisitedCourse ? 'resume' : 'start',
url: resumeCourseUrl,
});
};
// Below the course title alerts (appearing in the order listed here)
const courseStartAlert = useCourseStartAlert(courseId);
const courseEndAlert = useCourseEndAlert(courseId);
@@ -132,13 +126,6 @@ function OutlineTab({ intl }) {
<div className="col-12 col-sm-auto p-0">
<div role="heading" aria-level="1" className="h2">{title}</div>
</div>
{resumeCourseUrl && (
<div className="col-12 col-sm-auto p-0">
<Button variant="brand" block href={resumeCourseUrl} onClick={() => logResumeCourseClick()}>
{hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)}
</Button>
</div>
)}
</div>
{/** [MM-P2P] Experiment (className for optimizely trigger) */}
<div className="row course-outline-tab">
@@ -172,15 +159,18 @@ function OutlineTab({ intl }) {
<UpgradeToShiftDatesAlert model="outline" logUpgradeLinkClick={logUpgradeToShiftDatesLinkClick} />
</>
)}
{!courseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
{!deprecatedCourseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
<CourseGoalCard
courseId={courseId}
goalOptions={goalOptions}
title={title}
setGoalToDisplay={(newGoal) => { setCourseGoalToDisplay(newGoal); }}
setGoalToDisplay={(newGoal) => { setDeprecatedCourseGoalToDisplay(newGoal); }}
setGoalToastHeader={(newHeader) => { setGoalToastHeader(newHeader); }}
/>
)}
{resumeCourseUrl && (
<StartOrResumeCourseCard />
)}
<WelcomeMessage courseId={courseId} />
{rootCourseId && (
<>
@@ -211,15 +201,22 @@ function OutlineTab({ intl }) {
courseId={courseId}
username={username}
/>
{courseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
{deprecatedCourseGoalToDisplay && goalOptions && goalOptions.length > 0 && (
<UpdateGoalSelector
courseId={courseId}
goalOptions={goalOptions}
selectedGoal={courseGoalToDisplay}
setGoalToDisplay={(newGoal) => { setCourseGoalToDisplay(newGoal); }}
selectedGoal={deprecatedCourseGoalToDisplay}
setGoalToDisplay={(newGoal) => { setDeprecatedCourseGoalToDisplay(newGoal); }}
setGoalToastHeader={(newHeader) => { setGoalToastHeader(newHeader); }}
/>
)}
{weeklyLearningGoalEnabled && (
<WeeklyLearningGoalCard
daysPerWeek={selectedGoal && 'daysPerWeek' in selectedGoal ? selectedGoal.daysPerWeek : null}
subscribedToReminders={selectedGoal && 'subscribedToReminders' in selectedGoal ? selectedGoal.subscribedToReminders : false}
courseId={courseId}
/>
)}
<CourseTools
courseId={courseId}
/>

View File

@@ -6,6 +6,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter';
import Cookies from 'js-cookie';
import userEvent from '@testing-library/user-event';
import messages from './messages';
import { buildMinimalCourseBlocks } from '../../shared/data/__factories__/courseBlocks.factory';
import {
@@ -413,6 +414,68 @@ describe('Outline Tab', () => {
});
});
describe('Weekly Learning Goals', () => {
it('does not render weekly learning goal if weeklyLearningGoalEnabled is false', async () => {
await fetchAndRender();
expect(screen.queryByTestId('weekly-learning-goal-card')).not.toBeInTheDocument();
});
describe('weekly learning goal is not set', () => {
beforeEach(async () => {
setTabData({
course_goals: {
weekly_learning_goal_enabled: true,
},
});
await fetchAndRender();
});
it('renders weekly learning goal card', async () => {
expect(screen.queryByTestId('weekly-learning-goal-card')).toBeInTheDocument();
});
it('renders startOrResumeCourseCard', async () => {
expect(screen.queryByTestId('start-resume-card')).toBeInTheDocument();
});
it('disables the subscribe button if no goal is set', async () => {
expect(screen.getByLabelText(messages.setGoalReminder.defaultMessage)).toBeDisabled();
});
it('calls the API when a button is clicked', async () => {
expect(screen.queryByText(messages.casualGoalButtonText.defaultMessage)).toBeInTheDocument();
expect(screen.getByText(messages.casualGoalButtonText.defaultMessage).closest('button')).toBeInTheDocument();
// click on Casual goal
const button = await screen.getByText(messages.casualGoalButtonText.defaultMessage).closest('button');
fireEvent.click(button);
// Verify the request was made
await waitFor(() => {
expect(axiosMock.history.post[0].url).toMatch(goalUrl);
// subscribe is turned on automatically
expect(axiosMock.history.post[0].data).toMatch(`{"course_id":"${courseId}","days_per_week":3,"subscribed_to_reminders":true}`);
// verify that the additional info about subscriptions shows up
expect(screen.queryByText(messages.goalReminderDetail.defaultMessage)).toBeInTheDocument();
});
expect(screen.getByLabelText(messages.setGoalReminder.defaultMessage)).toBeEnabled();
// Click on subscribe to reminders
const subscriptionSwitch = await screen.getByRole('switch', { name: messages.setGoalReminder.defaultMessage });
expect(subscriptionSwitch).toBeInTheDocument();
fireEvent.click(subscriptionSwitch);
await waitFor(() => {
expect(axiosMock.history.post[1].url).toMatch(goalUrl);
expect(axiosMock.history.post[1].data)
.toMatch(`{"course_id":"${courseId}","days_per_week":3,"subscribed_to_reminders":false}`);
});
// verify that the additional info about subscriptions gets hidden
expect(screen.queryByText(messages.goalReminderDetail.defaultMessage)).not.toBeInTheDocument();
});
});
});
describe('Course Handouts', () => {
it('renders title when handouts are available', async () => {
await fetchAndRender();

View File

@@ -66,6 +66,15 @@ const messages = defineMessages({
defaultMessage: 'Open',
description: 'A button to open the given section of the course outline',
},
startBlurb: {
id: 'learning.outline.startBlurb',
defaultMessage: 'Begin your course today',
},
resumeBlurb: {
id: 'learning.outline.resumeBlurb',
defaultMessage: 'Pick up where you left off',
description: 'Text describing to the learner that they can return to the last section they visited within the course.',
},
resume: {
id: 'learning.outline.resume',
defaultMessage: 'Resume course',
@@ -74,6 +83,14 @@ const messages = defineMessages({
id: 'learning.outline.setGoal',
defaultMessage: 'To start, set a course goal by selecting the option below that best describes your learning plan.',
},
setWeeklyGoal: {
id: 'learning.outline.setWeeklyGoal',
defaultMessage: 'Set a weekly learning goal',
},
setWeeklyGoalDetail: {
id: 'learning.outline.setWeeklyGoalDetail',
defaultMessage: 'Setting a goal motivates you to finish the course. You can always change it later.',
},
start: {
id: 'learning.outline.start',
defaultMessage: 'Start Course',
@@ -112,6 +129,46 @@ const messages = defineMessages({
defaultMessage: 'Welcome to',
description: 'This precedes the title of the course',
},
setLearningGoalButtonScreenReaderText: {
id: 'learning.outline.goalButton.casual.title',
defaultMessage: 'Set a learning goal style.',
description: 'screen reader text informing learner they can select an intensity of learning goal',
},
casualGoalButtonTitle: {
id: 'learning.outline.goalButton.screenReader.text',
defaultMessage: 'Casual',
description: 'A very short description of the least intense of three learning goals',
},
casualGoalButtonText: {
id: 'learning.outline.goalButton.casual.text',
defaultMessage: '1 day a week',
},
regularGoalButtonTitle: {
id: 'learning.outline.goalButton.regular.title',
defaultMessage: 'Regular',
description: 'A very short description of the middle option of three learning goals, Casual, Regular and Intense',
},
regularGoalButtonText: {
id: 'learning.outline.goalButton.regular.text',
defaultMessage: '3 days a week',
},
intenseGoalButtonTitle: {
id: 'learning.outline.goalButton.intense.title',
defaultMessage: 'Intense',
description: 'A very short description of the most intensive option of three learning goals, Casual, Regular and Intense',
},
intenseGoalButtonText: {
id: 'learning.outline.goalButton.intense.text',
defaultMessage: '5 days a week',
},
setGoalReminder: {
id: 'learning.outline.setGoalReminder',
defaultMessage: 'Set a goal reminder',
},
goalReminderDetail: {
id: 'learning.outline.goalReminderDetail',
defaultMessage: 'If we notice youre not quite at your goal, well send you an email reminder.',
},
proctoringInfoPanel: {
id: 'learning.proctoringPanel.header',
defaultMessage: 'This course contains proctored exams',
@@ -220,6 +277,11 @@ const messages = defineMessages({
id: 'learning.proctoringPanel.onboardingButtonPastDue',
defaultMessage: 'Onboarding Past Due',
},
accountActivationAlertTitle: {
id: 'account-activation.alert.title',
defaultMessage: 'Activate your account so you can log back in',
description: 'Title for account activation alert which is shown after the registration',
},
});
export default messages;

View File

@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
function FlagButton({
ButtonIcon,
srText,
title,
text,
isEnabled,
handleSelect,
}) {
const [isSelected, setIsSelected] = useState(false);
return (
<button
type="button"
className={classNames('col flex-grow-1 p-3 border border-light rounded bg-white', { 'border-dark': isEnabled || isSelected })}
onMouseEnter={() => setIsSelected(true)}
onMouseLeave={() => setIsSelected(false)}
onClick={() => handleSelect()}
>
<div className=" justify-content-center">
{ButtonIcon}
</div>
<span className="sr-only sr-only-focusable">{srText}</span>
<div className="text-center small">
{title}
</div>
<div className="text-center micro">
{text}
</div>
</button>
);
}
FlagButton.propTypes = {
ButtonIcon: PropTypes.element.isRequired,
srText: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
text: PropTypes.string,
isEnabled: PropTypes.bool,
handleSelect: PropTypes.func.isRequired,
};
FlagButton.defaultProps = {
isEnabled: false,
text: '',
};
export default FlagButton;

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { Button, Card } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux';
import { sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import messages from '../messages';
import { useModel } from '../../../generic/model-store';
function StartOrResumeCourseCard({ intl }) {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const eventProperties = {
org_key: org,
courserun_key: courseId,
};
const {
resumeCourse: {
hasVisitedCourse,
url: resumeCourseUrl,
},
} = useModel('outline', courseId);
const logResumeCourseClick = () => {
sendTrackingLogEvent('edx.course.home.resume_course.clicked', {
...eventProperties,
event_type: hasVisitedCourse ? 'resume' : 'start',
url: resumeCourseUrl,
});
};
return (
<Card className="mb-3" data-testid="start-resume-card">
<Card.Body>
<div className="row w-100 m-0 ">
<h2 className="h4 col-auto flex-grow-1">{intl.formatMessage(messages.startBlurb)}</h2>
<div className="col col-auto p-0 justify-content-end">
<Button
variant="brand"
block
href={resumeCourseUrl}
onClick={() => logResumeCourseClick()}
>
{hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)}
</Button>
</div>
</div>
</Card.Body>
</Card>
);
}
StartOrResumeCourseCard.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(StartOrResumeCourseCard);

View File

@@ -0,0 +1,173 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Form, Card, Icon } from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Email } from '@edx/paragon/icons';
import { ReactComponent as FlagIntenseIcon } from '@edx/paragon/icons/svg/flag.svg';
import { ReactComponent as FlagCasualIcon } from './flag_outline.svg';
import { ReactComponent as FlagRegularIcon } from './flag_gray.svg';
import messages from '../messages';
import FlagButton from './FlagButton';
import { saveWeeklyCourseGoal } from '../../data';
function WeeklyLearningGoalCard({
daysPerWeek,
subscribedToReminders,
courseId,
intl,
}) {
const [daysPerWeekGoal, setDaysPerWeekGoal] = useState(daysPerWeek);
// eslint-disable-next-line react/prop-types
const [isGetReminderSelected, setGetReminderSelected] = useState(subscribedToReminders);
const weeklyLearningGoalLevels = {
CASUAL: 3,
REGULAR: 4,
INTENSE: 5,
};
Object.freeze(weeklyLearningGoalLevels);
function handleSelect(days) {
// Set the subscription button if this is the first time selecting a goal
const selectReminders = daysPerWeekGoal === null ? true : isGetReminderSelected;
setGetReminderSelected(selectReminders);
setDaysPerWeekGoal(days);
saveWeeklyCourseGoal(courseId, days, selectReminders);
}
function handleSubscribeToReminders(event) {
const isGetReminderChecked = event.target.checked;
setGetReminderSelected(isGetReminderChecked);
saveWeeklyCourseGoal(courseId, daysPerWeekGoal, isGetReminderChecked);
}
return (
<div className="row w-100 m-0 p-0">
<Card className="mb-3" data-testid="weekly-learning-goal-card">
<Card.Body>
<Card.Title>
<h4 className="m-0">{intl.formatMessage(messages.setWeeklyGoal)}</h4>
</Card.Title>
<Card.Text>
{intl.formatMessage(messages.setWeeklyGoalDetail)}
</Card.Text>
<div
className="row w-100 m-0 p-0 justify-content-around"
>
<label
htmlFor={weeklyLearningGoalLevels.CASUAL}
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
>
<input
type="radio"
id={weeklyLearningGoalLevels.CASUAL}
name="learningGoal"
radioGroup="learningGoal"
value={weeklyLearningGoalLevels.CASUAL}
onChange={() => handleSelect(weeklyLearningGoalLevels.CASUAL)}
tabIndex="0"
checked={weeklyLearningGoalLevels.CASUAL === daysPerWeekGoal}
className="position-absolute invisible"
/>
<FlagButton
ButtonIcon={<FlagCasualIcon />}
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
title={intl.formatMessage(messages.casualGoalButtonTitle)}
text={intl.formatMessage(messages.casualGoalButtonText)}
isEnabled={weeklyLearningGoalLevels.CASUAL === daysPerWeekGoal}
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.CASUAL); }}
/>
</label>
<label
htmlFor={weeklyLearningGoalLevels.REGULAR}
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
>
<input
type="radio"
id={weeklyLearningGoalLevels.REGULAR}
name="learningGoal"
radioGroup="learningGoal"
value={weeklyLearningGoalLevels.REGULAR}
onChange={() => handleSelect(weeklyLearningGoalLevels.REGULAR)}
tabIndex="-1"
checked={weeklyLearningGoalLevels.REGULAR === daysPerWeekGoal}
className="position-absolute invisible"
/>
<FlagButton
ButtonIcon={<FlagRegularIcon />}
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
title={intl.formatMessage(messages.regularGoalButtonTitle)}
text={intl.formatMessage(messages.regularGoalButtonText)}
isEnabled={weeklyLearningGoalLevels.REGULAR === daysPerWeekGoal}
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.REGULAR); }}
/>
</label>
<label
htmlFor={weeklyLearningGoalLevels.INTENSE}
className="col-auto col-md-12 col-xl-auto m-0 p-0 pb-md-3 pb-xl-0"
>
<input
type="radio"
id={weeklyLearningGoalLevels.INTENSE}
name="learningGoal"
radioGroup="learningGoal"
value={weeklyLearningGoalLevels.INTENSE}
onChange={() => handleSelect(weeklyLearningGoalLevels.INTENSE)}
tabIndex="-1"
checked={weeklyLearningGoalLevels.INTENSE === daysPerWeekGoal}
className="position-absolute invisible"
/>
<FlagButton
ButtonIcon={<FlagIntenseIcon />}
srText={intl.formatMessage(messages.setLearningGoalButtonScreenReaderText)}
title={intl.formatMessage(messages.intenseGoalButtonTitle)}
text={intl.formatMessage(messages.intenseGoalButtonText)}
isEnabled={weeklyLearningGoalLevels.INTENSE === daysPerWeekGoal}
handleSelect={() => { handleSelect(weeklyLearningGoalLevels.INTENSE); }}
/>
</label>
</div>
<div className="row p-3">
<Form.Switch
checked={isGetReminderSelected}
onChange={(event) => handleSubscribeToReminders(event)}
disabled={!daysPerWeekGoal}
>
{intl.formatMessage(messages.setGoalReminder)}
</Form.Switch>
</div>
</Card.Body>
{isGetReminderSelected && (
<Card.Footer className="border-0 px-2.5">
<div className="row w-100 m-0 small align-center">
<div className="d-flex align-items-center pr-1.5">
<Icon src={Email} />
</div>
<div className="col align-center">
{intl.formatMessage(messages.goalReminderDetail)}
</div>
</div>
</Card.Footer>
)}
</Card>
</div>
);
}
WeeklyLearningGoalCard.propTypes = {
daysPerWeek: PropTypes.number,
subscribedToReminders: PropTypes.bool,
courseId: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};
WeeklyLearningGoalCard.defaultProps = {
daysPerWeek: null,
subscribedToReminders: false,
};
export default injectIntl(WeeklyLearningGoalCard);

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="15"
height="17"
viewBox="0 0 15 17"
fill="none"
version="1.1"
id="svg11"
xmlns="http://www.w3.org/2000/svg">
<path
d="M9.4 2L9 0H0V17H2V10H7.6L8 12H15V2H9.4ZM13 10H9.64L9.24 8H2V2H7.36L7.76 4H13V10Z"
fill="#002B2B"
id="path9" />
<path
style="fill:#808080;fill-rule:evenodd;stroke-width:0.0150977"
d="M 9.6594698,9.9871226 C 9.6577909,9.9829707 9.5654776,9.5311723 9.4543296,8.9831261 L 9.2522415,7.9866785 5.6376662,7.9790074 2.0230906,7.9713362 V 4.9970494 2.0227625 l 2.6636151,0.00771 2.6636151,0.00771 0.1968204,0.9888987 0.1968205,0.9888988 h 2.6200263 2.620026 v 2.9893428 2.9893428 h -1.660746 c -0.91341,0 -1.6621194,-0.0034 -1.6637982,-0.00755 z"
id="path302" />
</svg>

After

Width:  |  Height:  |  Size: 801 B

View File

@@ -0,0 +1,3 @@
<svg width="15" height="17" viewBox="0 0 15 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.4 2L9 0H0V17H2V10H7.6L8 12H15V2H9.4ZM13 10H9.64L9.24 8H2V2H7.36L7.76 4H13V10Z" fill="#002B2B"/>
</svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@@ -90,8 +90,6 @@ function Course({
courseId={courseId}
sectionId={section ? section.id : null}
sequenceId={sequenceId}
isStaff={course ? course.isStaff : null}
unitId={unitId}
//* * [MM-P2P] Experiment */
mmp2p={MMP2P}
/>

View File

@@ -1,19 +1,36 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHome } from '@fortawesome/free-solid-svg-icons';
import { useSelector } from 'react-redux';
import { SelectMenu } from '@edx/paragon';
import { Hyperlink, MenuItem, SelectMenu } from '@edx/paragon';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import {
sendTrackingLogEvent,
sendTrackEvent,
} from '@edx/frontend-platform/analytics';
import { useModel, useModels } from '../../generic/model-store';
/** [MM-P2P] Experiment */
import { MMP2PFlyoverTrigger } from '../../experiments/mm-p2p';
import JumpNavMenuItem from './JumpNavMenuItem';
function CourseBreadcrumb({
content, withSeparator, courseId, unitId, isStaff,
content, withSeparator,
}) {
const defaultContent = content.filter(destination => destination.default)[0] || { id: courseId, label: '' };
const defaultContent = content.filter(destination => destination.default)[0];
const administrator = getAuthenticatedUser() ? getAuthenticatedUser().administrator : false;
function logEvent(target) {
const eventName = 'edx.ui.lms.jump_nav.selected';
const payload = {
target_name: target.label,
id: target.id,
current_id: defaultContent.id,
widget_placement: 'breadcrumb',
};
sendTrackEvent(eventName, payload);
sendTrackingLogEvent(eventName, payload);
}
return (
<>
@@ -27,22 +44,22 @@ function CourseBreadcrumb({
whiteSpace: 'nowrap',
}}
>
{content.length < 2 || !isStaff
{ getConfig().ENABLE_JUMPNAV !== 'true' || content.length < 2 || !administrator
? (
<a className="text-primary-500" href={`/course/${courseId}/${defaultContent.id}`}>
{defaultContent.label}
<a className="text-primary-500" href={defaultContent.url}>{defaultContent.label}
</a>
)
: (
<SelectMenu isLink defaultMessage={defaultContent.label}>
{content.map(item => (
<JumpNavMenuItem
isDefault={item.default}
sequences={item.sequences}
courseId={courseId}
title={item.label}
currentUnit={unitId}
/>
<MenuItem
as={Hyperlink}
defaultSelected={item.default}
destination={item.url}
onClick={logEvent(item)}
>
{item.label}
</MenuItem>
))}
</SelectMenu>
)}
@@ -55,74 +72,58 @@ CourseBreadcrumb.propTypes = {
content: PropTypes.arrayOf(
PropTypes.shape({
default: PropTypes.bool,
url: PropTypes.string,
id: PropTypes.string,
label: PropTypes.string,
}),
).isRequired,
unitId: PropTypes.string,
withSeparator: PropTypes.bool,
courseId: PropTypes.string,
isStaff: PropTypes.bool,
};
CourseBreadcrumb.defaultProps = {
withSeparator: false,
unitId: null,
courseId: null,
isStaff: null,
};
export default function CourseBreadcrumbs({
courseId,
sectionId,
sequenceId,
unitId,
isStaff,
/** [MM-P2P] Experiment */
mmp2p,
}) {
const course = useModel('coursewareMeta', courseId);
const courseStatus = useSelector(state => state.courseware.courseStatus);
const sections = course ? Object.fromEntries(useModels('sections', course.sectionIds).map(section => [section.id, section])) : null;
const possibleSequences = sections && sectionId ? sections[sectionId].sequenceIds : [];
const sequences = Object.fromEntries(useModels('sequences', possibleSequences).map(sequence => [sequence.id, sequence]));
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const allSequencesInSections = Object.fromEntries(useModels('sections', course.sectionIds).map(section => [section.id, {
default: section.id === sectionId,
title: section.title,
sequences: useModels('sequences', section.sequenceIds),
}]));
const links = useMemo(() => {
const chapters = [];
const sequentials = [];
const temp = [];
if (courseStatus === 'loaded' && sequenceStatus === 'loaded') {
Object.entries(allSequencesInSections).forEach(([id, section]) => {
chapters.push({
id,
label: section.title,
default: section.default,
sequences: section.sequences,
});
if (section.default) {
section.sequences.forEach(sequence => {
sequentials.push({
id: sequence.id,
label: sequence.title,
default: sequence.id === sequenceId,
sequences: [sequence],
});
});
}
});
temp.push(course.sectionIds.map(id => ({
id,
label: sections[id].title,
default: (id === sectionId),
// navigate to first sequence in section, (TODO: navigate to first incomplete sequence in section)
url: `${getConfig().BASE_URL}/course/${courseId}/${sections[id].sequenceIds[0]}`,
})));
temp.push(sections[sectionId].sequenceIds.map(id => ({
id,
label: sequences[id].title,
default: id === sequenceId,
// first unit it section (TODO: navigate to first incomplete in sequence)
url: `${getConfig().BASE_URL}/course/${courseId}/${sequences[id].id}/${sequences[id].unitIds[0]}`,
})));
}
return [chapters, sequentials];
}, [courseStatus, sequenceStatus, allSequencesInSections]);
return temp;
}, [courseStatus, sections, sequences]);
return (
<nav aria-label="breadcrumb" className="my-4 d-inline-block col-sm-10">
<ol className="list-unstyled d-flex flex-nowrap align-items-center m-0">
<li className="list-unstyled d-flex m-0">
<ol className="list-unstyled d-flex align-items-center m-0">
<li>
<a
href={`/course/${courseId}/home`}
href={`${getConfig().LMS_BASE_URL}/courses/${courseId}/course/`}
className="flex-shrink-0 text-primary"
>
<FontAwesomeIcon icon={faHome} className="mr-2" />
@@ -135,12 +136,8 @@ export default function CourseBreadcrumbs({
</li>
{links.map(content => (
<CourseBreadcrumb
courseId={courseId}
sequenceId={sequenceId}
content={content}
unitId={unitId}
withSeparator
isStaff={isStaff}
/>
))}
{/** [MM-P2P] Experiment */}
@@ -156,8 +153,6 @@ CourseBreadcrumbs.propTypes = {
courseId: PropTypes.string.isRequired,
sectionId: PropTypes.string,
sequenceId: PropTypes.string,
unitId: PropTypes.string,
isStaff: PropTypes.bool,
/** [MM-P2P] Experiment */
mmp2p: PropTypes.shape({
state: PropTypes.shape({
@@ -169,8 +164,6 @@ CourseBreadcrumbs.propTypes = {
CourseBreadcrumbs.defaultProps = {
sectionId: null,
sequenceId: null,
unitId: null,
isStaff: null,
/** [MM-P2P] Experiment */
mmp2p: {},
};

View File

@@ -1,28 +1,23 @@
import React from 'react';
import { screen, render } from '@testing-library/react';
import { screen, render, fireEvent } from '@testing-library/react';
import { useSelector } from 'react-redux';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';
import { useModel, useModels } from '../../generic/model-store';
import CourseBreadcrumbs from './CourseBreadcrumbs';
jest.mock('@edx/frontend-platform');
jest.mock('react-redux');
jest.mock('@edx/frontend-platform/analytics');
// Remove When Fully rolled out>>>
jest.mock('../../generic/model-store');
jest.mock('@edx/frontend-platform/auth');
getConfig.mockImplementation(() => ({ ENABLE_JUMPNAV: 'true' }));
getAuthenticatedUser.mockImplementation(() => ({ administrator: true }));
// ^^^^Remove When Fully rolled out
jest.mock('react-redux', () => ({
connect: (mapStateToProps, mapDispatchToProps) => (ReactComponent) => ({
mapStateToProps,
mapDispatchToProps,
ReactComponent,
}),
Provider: ({ children }) => children,
useSelector: () => 'loaded',
}));
useSelector.mockImplementation(() => 'loaded');
useModels.mockImplementation((name) => {
if (name === 'sections') {
return [
@@ -107,14 +102,16 @@ describe('CourseBreadcrumbs', () => {
courseId="course-v1:edX+DemoX+Demo_Course"
sectionId="block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations"
sequenceId="block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions"
isStaff
/>,
);
it('renders course breadcrumbs as expected', async () => {
expect(screen.queryAllByRole('link')).toHaveLength(1);
const courseHomeButtonDestination = screen.getAllByRole('link')[0].href;
expect(courseHomeButtonDestination).toBe('http://localhost/course/course-v1:edX+DemoX+Demo_Course/home');
it('renders course breadcrumbs as expected, handles clicks', async () => {
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
expect(screen.queryAllByRole('button')).toHaveLength(2);
const sectionButton = screen.getByText('Example Week 1: Getting Started');
expect(screen.queryAllByRole('link')).toHaveLength(1);
fireEvent.click(sectionButton);
expect(screen.queryAllByRole('link')).toHaveLength(2);
const menuItem = screen.queryAllByRole('link')[0];
fireEvent.click(menuItem);
});
});

View File

@@ -1,80 +0,0 @@
/* eslint-disable consistent-return */
import React from 'react';
import PropTypes from 'prop-types';
import { history } from '@edx/frontend-platform';
import { MenuItem } from '@edx/paragon';
import {
sendTrackingLogEvent,
sendTrackEvent,
} from '@edx/frontend-platform/analytics';
import { useDispatch } from 'react-redux';
import { checkBlockCompletion } from '../data';
export default function JumpNavMenuItem({
title,
courseId,
currentUnit,
sequences,
isDefault,
}) {
const dispatch = useDispatch();
function logEvent(targetUrl) {
const eventName = 'edx.ui.lms.jump_nav.selected';
const payload = {
target_name: title,
id: targetUrl,
current_id: courseId,
widget_placement: 'breadcrumb',
};
sendTrackEvent(eventName, payload);
sendTrackingLogEvent(eventName, payload);
}
function lazyloadUrl() {
if (isDefault) {
return `/course/${courseId}/${currentUnit}`;
}
const destinationString = sequences.forEach(sequence => sequence.unitIds.forEach(unitId => {
const complete = dispatch(checkBlockCompletion(
courseId,
sequence.id, unitId,
))
.then(value => value);
if (!complete) { return `/course/${courseId}/${unitId}`; }
}));
return destinationString || `/course/${courseId}/${sequences[0].unitIds[0]}`;
}
function handleClick() {
const url = lazyloadUrl();
logEvent(url);
history.push(url);
}
return (
<MenuItem
defaultSelected={isDefault}
onClick={() => handleClick()}
>
{title}
</MenuItem>
);
}
const sequenceShape = PropTypes.shape({
id: PropTypes.string.isRequired,
unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
sectionId: PropTypes.string.isRequired,
isTimeLimited: PropTypes.bool,
isProctored: PropTypes.bool,
legacyWebUrl: PropTypes.string,
});
JumpNavMenuItem.propTypes = {
title: PropTypes.string.isRequired,
sequences: PropTypes.arrayOf(sequenceShape).isRequired,
isDefault: PropTypes.bool.isRequired,
courseId: PropTypes.string.isRequired,
currentUnit: PropTypes.string.isRequired,
};

View File

@@ -1,69 +0,0 @@
import React from 'react';
import { screen, render } from '@testing-library/react';
import JumpNavMenuItem from './JumpNavMenuItem';
import { fireEvent } from '../../setupTest';
jest.mock('@edx/frontend-platform');
jest.mock('@edx/frontend-platform/analytics');
const mockCheckBlock = jest.fn(() => Promise.resolve(true)); // check all units
jest.mock('react-redux', () => ({
useDispatch: () => mockCheckBlock,
}));
const mockData = {
sectionId: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations',
sequenceId: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions',
title: 'Demo Menu Item',
courseId: 'course-v1:edX+DemoX+Demo_Course',
currentUnit: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations',
sequences: [
{
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5',
blockType: 'sequential',
unitIds: [
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@867dddb6f55d410caaa9c1eb9c6743ec',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4f6c1b4e316a419ab5b6bf30e6c708e9',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@3dc16db8d14842e38324e95d4030b8a0',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@4a1bba2a403f40bca5ec245e945b0d76',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@256f17a44983429fb1a60802203ee4e0',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@e3601c0abee6427d8c17e6d6f8fdddd1',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@134df56c516a4a0dbb24dd5facef746e',
],
legacyWebUrl: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@sequential+block@19a30717eff543078a5d94ae9d6c18a5?experience=legacy',
sectionId: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations',
},
{
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions',
title: 'Homework - Question Styles',
legacyWebUrl: 'http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course/jump_to/block-v1:edX+DemoX+Demo_Course+type@sequential+block@basic_questions?experience=legacy',
unitIds: [
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2152d4a4aadc4cb0af5256394a3d1fc7',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@47dbd5f836544e61877a483c0b75606c',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@54bb9b142c6c4c22afc62bcb628f0e68',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_0c92347a5c00',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@vertical_1fef54c2b23b',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@2889db1677a549abb15eb4d886f95d1c',
'block-v1:edX+DemoX+Demo_Course+type@vertical+block@e8a5cc2aed424838853defab7be45e42',
],
sectionId: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@interactive_demonstrations',
},
],
isDefault: false,
};
describe('JumpNavMenuItem', () => {
render(
<JumpNavMenuItem
{...mockData}
/>,
);
it('renders menu Item as expected with button and Text and handles clicks', () => {
expect(screen.queryAllByRole('button')).toHaveLength(1);
expect(screen.getByText('Demo Menu Item'));
const navButton = screen.queryAllByRole('button')[0];
fireEvent.click(navButton);
expect(mockCheckBlock).toBeCalledTimes(14); // number of units to check on load.
});
});

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
@@ -11,7 +11,6 @@ import CourseInProgress from './CourseInProgress';
import CourseNonPassing from './CourseNonPassing';
import { COURSE_EXIT_MODES, getCourseExitMode } from './utils';
import messages from './messages';
import { unsubscribeFromGoalReminders } from './data/thunks';
import { useModel } from '../../../generic/model-store';
@@ -19,13 +18,10 @@ function CourseExit({ intl }) {
const { courseId } = useSelector(state => state.courseware);
const {
certificateData,
courseExitPageIsActive,
courseGoals,
enrollmentMode,
hasScheduledContent,
isEnrolled,
isMasquerading,
userHasPassingGrade,
courseExitPageIsActive,
} = useModel('coursewareMeta', courseId);
const mode = getCourseExitMode(
@@ -36,15 +32,6 @@ function CourseExit({ intl }) {
courseExitPageIsActive,
);
// Audit users cannot fully complete a course, so we will
// unsubscribe them from goal reminders once they reach the course exit page
// to avoid spamming them with goal reminder emails
if (courseGoals && enrollmentMode === 'audit' && !isMasquerading) {
useEffect(() => {
unsubscribeFromGoalReminders(courseId);
}, []);
}
let body = null;
if (mode === COURSE_EXIT_MODES.nonPassing) {
body = (<CourseNonPassing />);

View File

@@ -3,7 +3,6 @@ import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { waitFor } from '@testing-library/react';
import { fetchCourse } from '../../data';
import { buildSimpleCourseBlocks } from '../../../shared/data/__factories__/courseBlocks.factory';
@@ -378,25 +377,4 @@ describe('Course Exit Pages', () => {
expect(screen.getByRole('link', { name: 'View course schedule' })).toBeInTheDocument();
});
});
it('unsubscribes the user when loading the course exit page', async () => {
setMetadata({
enrollment: {
mode: 'audit',
courseGoals: {
goal_options: [],
selected_goal: {
days_per_week: 1,
subscribed_to_reminders: true,
},
},
},
});
await fetchAndRender(<CourseExit />);
const url = `${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`;
await waitFor(() => {
expect(axiosMock.history.post[0].url).toMatch(url);
expect(axiosMock.history.post[0].data).toMatch(`{"course_id":"${defaultMetadata.id}","subscribed_to_reminders":false}`);
});
});
});

View File

@@ -23,7 +23,7 @@ function filterRecommendationsList(
));
}
export async function getCourseRecommendations(courseKey) {
export default async function getCourseRecommendations(courseKey) {
const discoveryApiUrl = getConfig().DISCOVERY_API_BASE_URL;
if (!discoveryApiUrl) {
return [];
@@ -36,11 +36,3 @@ export async function getCourseRecommendations(courseKey) {
]);
return filterRecommendationsList(camelCaseObject(recommendationsResponse), camelCaseObject(enrollmentsResponse));
}
export async function postUnsubscribeFromGoalReminders(courseId) {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_home/save_course_goal`);
return getAuthenticatedHttpClient().post(url.href, {
course_id: courseId,
subscribed_to_reminders: false,
});
}

View File

@@ -5,10 +5,7 @@ import {
fetchCourseRecommendationsRequest,
fetchCourseRecommendationsSuccess,
} from './slice';
import {
getCourseRecommendations,
postUnsubscribeFromGoalReminders,
} from './api';
import getCourseRecommendations from './api';
import { updateModel } from '../../../../generic/model-store';
export default function fetchCourseRecommendations(courseKey, courseId) {
@@ -30,7 +27,3 @@ export default function fetchCourseRecommendations(courseKey, courseId) {
}
};
}
export async function unsubscribeFromGoalReminders(courseId, daysPerWeek, subscribedToReminders) {
return postUnsubscribeFromGoalReminders(courseId, daysPerWeek, subscribedToReminders);
}

View File

@@ -5,19 +5,19 @@ import { getConfig, history } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ActionRow, Alert, Button } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store';
import { saveIntegritySignature } from '../../../data';
import messages from './messages';
function HonorCode({ intl, courseId }) {
const dispatch = useDispatch();
const { isMasquerading } = useModel('coursewareMeta', courseId);
const siteName = getConfig().SITE_NAME;
const honorCodeUrl = `${getConfig().TERMS_OF_SERVICE_URL}#honor-code`;
const honorCodeUrl = `${process.env.TERMS_OF_SERVICE_URL}#honor-code`;
const handleCancel = () => history.push(`/course/${courseId}/home`);
const handleAgree = () => dispatch(saveIntegritySignature(courseId, isMasquerading));
const handleAgree = () => {
dispatch(saveIntegritySignature(courseId));
};
return (
<Alert variant="light" aria-live="off">

View File

@@ -8,13 +8,6 @@ Factory.define('courseMetadata')
.attrs({
content_type_gating_enabled: false,
course_expired_message: null,
course_goals: {
goal_options: [],
selected_goal: {
days_per_week: 1,
subscribed_to_reminders: true,
},
},
end: null,
enrollment_start: null,
enrollment_end: null,

View File

@@ -187,7 +187,6 @@ function normalizeMetadata(metadata) {
accessExpiration: camelCaseObject(data.access_expiration),
canShowUpgradeSock: data.can_show_upgrade_sock,
contentTypeGatingEnabled: data.content_type_gating_enabled,
courseGoals: data.course_goals,
id: data.id,
title: data.name,
number: data.number,

View File

@@ -247,7 +247,7 @@ export function checkBlockCompletion(courseId, sequenceId, unitId) {
return async (dispatch, getState) => {
const { models } = getState();
if (models.units[unitId].complete) {
return {}; // do nothing. Things don't get uncompleted after they are completed.
return; // do nothing. Things don't get uncompleted after they are completed.
}
try {
@@ -259,11 +259,9 @@ export function checkBlockCompletion(courseId, sequenceId, unitId) {
complete: isComplete,
},
}));
return isComplete;
} catch (error) {
logError(error);
}
return {};
};
}
@@ -303,15 +301,10 @@ export function saveSequencePosition(courseId, sequenceId, activeUnitIndex) {
};
}
export function saveIntegritySignature(courseId, isMasquerading) {
export function saveIntegritySignature(courseId) {
return async (dispatch) => {
try {
// If the request is made by a staff user masquerading as a learner,
// don't actually create a signature for them on the backend. Only
// frontend state will be updated.
if (!isMasquerading) {
await postIntegritySignature(courseId);
}
await postIntegritySignature(courseId);
dispatch(updateModel({
modelType: 'coursewareMeta',
model: {

View File

@@ -1,35 +0,0 @@
import React, { useEffect, useState } from 'react';
import { getConfig } from '@edx/frontend-platform';
import PropTypes from 'prop-types';
import { getNotices } from './api';
/**
* This component uses the platform-plugin-notices plugin to function.
* If the user has an unacknowledged notice, they will be rerouted off
* course home and onto a full-screen notice page. If the plugin is not
* installed, or there are no notices, we just passthrough this component.
*/
const NoticesProvider = ({ children }) => {
const [isRedirected, setIsRedirected] = useState();
useEffect(async () => {
if (getConfig().ENABLE_NOTICES) {
const data = await getNotices();
if (data && data.results && data.results.length > 0) {
const { results } = data;
setIsRedirected(true);
window.location.replace(`${results[0]}?next=${window.location.href}`);
}
}
}, []);
return (
<div>
{isRedirected === true ? null : children}
</div>
);
};
NoticesProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export default NoticesProvider;

View File

@@ -1,61 +0,0 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import {
initializeMockApp,
render,
act,
} from '../../setupTest';
import NoticesProvider from './NoticesProvider';
import { getNotices } from './api';
jest.mock('./api', () => ({
getNotices: jest.fn(),
}));
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(),
}));
describe('NoticesProvider', () => {
beforeAll(async () => {
jest.resetModules();
await initializeMockApp();
});
function buildAndRender() {
render(
<NoticesProvider>
<div />
</NoticesProvider>,
);
}
it('does not call api if ENABLE_NOTICES is false', () => {
getConfig.mockImplementation(() => ({ ENABLE_NOTICES: false }));
buildAndRender();
expect(getNotices).toHaveBeenCalledTimes(0);
});
it('redirects user on notice returned from API', async () => {
const redirectUrl = 'http://example.com/test_route';
getConfig.mockImplementation(() => ({ ENABLE_NOTICES: true }));
getNotices.mockImplementation(() => ({ results: [redirectUrl] }));
delete window.location;
window.location = { replace: jest.fn() };
process.env.ENABLE_NOTICES = true;
await act(async () => buildAndRender());
expect(window.location.replace).toHaveBeenCalledWith(`${redirectUrl}?next=${window.location.href}`);
});
it('does not redirect on no data', async () => {
getNotices.mockImplementation(() => ({}));
getConfig.mockImplementation(() => ({ ENABLE_NOTICES: true }));
delete window.location;
window.location = { replace: jest.fn() };
process.env.ENABLE_NOTICES = true;
await act(async () => buildAndRender());
expect(window.location.replace).toHaveBeenCalledTimes(0);
expect(window.location.toString() === 'http://localhost/');
});
});

View File

@@ -1,25 +0,0 @@
/* eslint-disable import/prefer-default-export */
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { logError, logInfo } from '@edx/frontend-platform/logging';
export const getNotices = async () => {
const authenticatedUser = getAuthenticatedUser();
const url = new URL(`${getConfig().LMS_BASE_URL}/notices/api/v1/unacknowledged`);
if (authenticatedUser) {
try {
const { data } = await getAuthenticatedHttpClient().get(url.href, {});
return data;
} catch (e) {
// we will just swallow error, as that probably means the notices app is not installed.
// Notices are not necessary for the rest of courseware to function.
const { customAttributes: { httpErrorStatus } } = e;
if (httpErrorStatus === 404) {
logInfo(`${e}. This probably happened because the notices plugin is not installed on platform.`);
} else {
logError(e);
}
}
}
return null;
};

View File

@@ -1 +0,0 @@
export { default } from './NoticesProvider';

View File

@@ -28,57 +28,54 @@ import { TabContainer } from './tab-page';
import { fetchDatesTab, fetchOutlineTab, fetchProgressTab } from './course-home/data';
import { fetchCourse } from './courseware/data';
import initializeStore from './store';
import NoticesProvider from './generic/notices';
subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider store={initializeStore()}>
<NoticesProvider>
<UserMessagesProvider>
<Switch>
<PageRoute exact path="/goal-unsubscribe/:token" component={GoalUnsubscribe} />
<PageRoute path="/redirect" component={CoursewareRedirectLandingPage} />
<PageRoute path="/course/:courseId/home">
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome">
<OutlineTab />
<UserMessagesProvider>
<Switch>
<PageRoute exact path="/goal-unsubscribe/:token" component={GoalUnsubscribe} />
<PageRoute path="/redirect" component={CoursewareRedirectLandingPage} />
<PageRoute path="/course/:courseId/home">
<TabContainer tab="outline" fetch={fetchOutlineTab} slice="courseHome">
<OutlineTab />
</TabContainer>
</PageRoute>
<PageRoute path="/course/:courseId/dates">
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
<DatesTab />
</TabContainer>
</PageRoute>
<PageRoute
path={[
'/course/:courseId/progress/:targetUserId/',
'/course/:courseId/progress',
]}
render={({ match }) => (
<TabContainer
tab="progress"
fetch={(courseId) => fetchProgressTab(courseId, match.params.targetUserId)}
slice="courseHome"
>
<ProgressTab />
</TabContainer>
</PageRoute>
<PageRoute path="/course/:courseId/dates">
<TabContainer tab="dates" fetch={fetchDatesTab} slice="courseHome">
<DatesTab />
</TabContainer>
</PageRoute>
<PageRoute
path={[
'/course/:courseId/progress/:targetUserId/',
'/course/:courseId/progress',
]}
render={({ match }) => (
<TabContainer
tab="progress"
fetch={(courseId) => fetchProgressTab(courseId, match.params.targetUserId)}
slice="courseHome"
>
<ProgressTab />
</TabContainer>
)}
/>
<PageRoute path="/course/:courseId/course-end">
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
<CourseExit />
</TabContainer>
</PageRoute>
<PageRoute
path={[
'/course/:courseId/:sequenceId/:unitId',
'/course/:courseId/:sequenceId',
'/course/:courseId',
]}
component={CoursewareContainer}
/>
</Switch>
</UserMessagesProvider>
</NoticesProvider>
)}
/>
<PageRoute path="/course/:courseId/course-end">
<TabContainer tab="courseware" fetch={fetchCourse} slice="courseware">
<CourseExit />
</TabContainer>
</PageRoute>
<PageRoute
path={[
'/course/:courseId/:sequenceId/:unitId',
'/course/:courseId/:sequenceId',
'/course/:courseId',
]}
component={CoursewareContainer}
/>
</Switch>
</UserMessagesProvider>
</AppProvider>,
document.getElementById('root'),
);
@@ -95,7 +92,7 @@ initialize({
CONTACT_URL: process.env.CONTACT_URL || null,
CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL || null,
ENTERPRISE_LEARNER_PORTAL_HOSTNAME: process.env.ENTERPRISE_LEARNER_PORTAL_HOSTNAME || null,
ENABLE_NOTICES: process.env.ENABLE_NOTICES || null,
ENABLE_JUMPNAV: process.env.ENABLE_JUMPNAV || null,
INSIGHTS_BASE_URL: process.env.INSIGHTS_BASE_URL || null,
SEARCH_CATALOG_URL: process.env.SEARCH_CATALOG_URL || null,
SOCIAL_UTM_MILESTONE_CAMPAIGN: process.env.SOCIAL_UTM_MILESTONE_CAMPAIGN || null,
@@ -104,7 +101,6 @@ initialize({
SUPPORT_URL_CALCULATOR_MATH: process.env.SUPPORT_URL_CALCULATOR_MATH || null,
SUPPORT_URL_ID_VERIFICATION: process.env.SUPPORT_URL_ID_VERIFICATION || null,
SUPPORT_URL_VERIFIED_CERTIFICATE: process.env.SUPPORT_URL_VERIFIED_CERTIFICATE || null,
TERMS_OF_SERVICE_URL: process.env.TERMS_OF_SERVICE_URL || null,
TWITTER_HASHTAG: process.env.TWITTER_HASHTAG || null,
TWITTER_URL: process.env.TWITTER_URL || null,
}, 'LearnerAppConfig');