Compare commits
8 Commits
dependabot
...
release/ul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
362a2962af | ||
|
|
80760103a2 | ||
|
|
bee0afd611 | ||
|
|
0418a04fff | ||
|
|
5061391122 | ||
|
|
deb7cef005 | ||
|
|
9bd9d31599 | ||
|
|
aec68e7c18 |
199
package-lock.json
generated
199
package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "0.2.6",
|
"@fortawesome/react-fontawesome": "0.2.6",
|
||||||
|
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||||
"@openedx/paragon": "^23.4.2",
|
"@openedx/paragon": "^23.4.2",
|
||||||
"@optimizely/react-sdk": "^2.9.1",
|
"@optimizely/react-sdk": "^2.9.1",
|
||||||
"@redux-devtools/extension": "3.3.0",
|
"@redux-devtools/extension": "3.3.0",
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
"redux": "4.2.1",
|
"redux": "4.2.1",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-mock-store": "1.5.5",
|
"redux-mock-store": "1.5.5",
|
||||||
"redux-saga": "1.3.0",
|
"redux-saga": "1.4.2",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "0.14.1",
|
"regenerator-runtime": "0.14.1",
|
||||||
"reselect": "5.1.1",
|
"reselect": "5.1.1",
|
||||||
@@ -2537,9 +2538,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@edx/frontend-platform": {
|
"node_modules/@edx/frontend-platform": {
|
||||||
"version": "8.5.1",
|
"version": "8.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-8.5.2.tgz",
|
||||||
"integrity": "sha512-8u3EdO0o7xX4vqorjOx3k2wbs2bu3DXlIA3bnD+Y56vSB5QYw6k5GzYqo9pPaTMGeq9TuLRvPLE/QFFlc3xvPg==",
|
"integrity": "sha512-YlxNWs8NW/I7F03k/jH6grWIuY/GJrspq7fqWm5K0ocvNEf+B8XKcaLUof+jVUuCItK93SoVRDZewwejnjty5w==",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cospired/i18n-iso-languages": "4.2.0",
|
"@cospired/i18n-iso-languages": "4.2.0",
|
||||||
@@ -7060,10 +7061,87 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@openedx/frontend-plugin-framework": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@openedx/frontend-plugin-framework/-/frontend-plugin-framework-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-8tGkuHvtzhbqb9dU4sXUtR0K44+Hjh1uGR6DvhZAt9wSKQC1v4RBk34ef8DFzQhoNQa/Jtn6BJuta4Un6MmHmw==",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"core-js": "3.37.1",
|
||||||
|
"react-redux": "8.1.1",
|
||||||
|
"redux": "4.2.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@edx/frontend-platform": "^7.0.0 || ^8.0.0",
|
||||||
|
"@openedx/paragon": "^21.0.0 || ^22.0.0 || ^23.0.0",
|
||||||
|
"prop-types": "^15.8.0",
|
||||||
|
"react": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^17.0.0 || ^18.0.0",
|
||||||
|
"react-error-boundary": "^4.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openedx/frontend-plugin-framework/node_modules/core-js": {
|
||||||
|
"version": "3.37.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz",
|
||||||
|
"integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@openedx/frontend-plugin-framework/node_modules/react-is": {
|
||||||
|
"version": "18.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
|
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@openedx/frontend-plugin-framework/node_modules/react-redux": {
|
||||||
|
"version": "8.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
|
||||||
|
"integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"react-is": "^18.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-native": ">=0.59",
|
||||||
|
"redux": "^4 || ^5.0.0-beta.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@openedx/paragon": {
|
"node_modules/@openedx/paragon": {
|
||||||
"version": "23.14.9",
|
"version": "23.15.2",
|
||||||
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.14.9.tgz",
|
"resolved": "https://registry.npmjs.org/@openedx/paragon/-/paragon-23.15.2.tgz",
|
||||||
"integrity": "sha512-IwM6UZJE6lKmGCyGV52oO+40m0y2kFAoOdzyFdw3ud+Wc0Ro3bcflPnuNuq2Wq5a3ceJ9RLu9g+uKx0Yv9yvrw==",
|
"integrity": "sha512-qHiEwcBBmvlXQ13j+bE3VaL9UEpb37ZLpR3+iryiWb+GZPva9VhZkvTMHORklD3jcFCoNt2TkKWZ906sxPJ0zw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -7775,17 +7853,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/core": {
|
"node_modules/@redux-saga/core": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.4.2.tgz",
|
||||||
"integrity": "sha512-L+i+qIGuyWn7CIg7k1MteHGfttKPmxwZR5E7OsGikCL2LzYA0RERlaUY00Y3P3ZV2EYgrsYlBrGs6cJP5OKKqA==",
|
"integrity": "sha512-nIMLGKo6jV6Wc1sqtVQs1iqbB3Kq20udB/u9XEaZQisT6YZ0NRB8+4L6WqD/E+YziYutd27NJbG8EWUPkb7c6Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.6.3",
|
"@babel/runtime": "^7.28.4",
|
||||||
"@redux-saga/deferred": "^1.2.1",
|
"@redux-saga/deferred": "^1.3.1",
|
||||||
"@redux-saga/delay-p": "^1.2.1",
|
"@redux-saga/delay-p": "^1.3.1",
|
||||||
"@redux-saga/is": "^1.1.3",
|
"@redux-saga/is": "^1.2.1",
|
||||||
"@redux-saga/symbols": "^1.1.3",
|
"@redux-saga/symbols": "^1.2.1",
|
||||||
"@redux-saga/types": "^1.2.1",
|
"@redux-saga/types": "^1.3.1",
|
||||||
"typescript-tuple": "^2.2.1"
|
"typescript-tuple": "^2.2.1"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
@@ -7794,40 +7872,40 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/deferred": {
|
"node_modules/@redux-saga/deferred": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.3.1.tgz",
|
||||||
"integrity": "sha512-cmin3IuuzMdfQjA0lG4B+jX+9HdTgHZZ+6u3jRAOwGUxy77GSlTi4Qp2d6PM1PUoTmQUR5aijlA39scWWPF31g==",
|
"integrity": "sha512-0YZ4DUivWojXBqLB/TmuRRpDDz7tyq1I0AuDV7qi01XlLhM5m51W7+xYtIckH5U2cMlv9eAuicsfRAi1XHpXIg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/delay-p": {
|
"node_modules/@redux-saga/delay-p": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.3.1.tgz",
|
||||||
"integrity": "sha512-MdiDxZdvb1m+Y0s4/hgdcAXntpUytr9g0hpcOO1XFVyyzkrDu3SKPgBFOtHn7lhu7n24ZKIAT1qtKyQjHqRd+w==",
|
"integrity": "sha512-597I7L5MXbD/1i3EmcaOOjL/5suxJD7p5tnbV1PiWnE28c2cYiIHqmSMK2s7us2/UrhOL2KTNBiD0qBg6KnImg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redux-saga/symbols": "^1.1.3"
|
"@redux-saga/symbols": "^1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/is": {
|
"node_modules/@redux-saga/is": {
|
||||||
"version": "1.1.3",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.2.1.tgz",
|
||||||
"integrity": "sha512-naXrkETG1jLRfVfhOx/ZdLj0EyAzHYbgJWkXbB3qFliPcHKiWbv/ULQryOAEKyjrhiclmr6AMdgsXFyx7/yE6Q==",
|
"integrity": "sha512-x3aWtX3GmQfEvn8dh0ovPbsXgK9JjpiR24wKztpGbZP8JZUWWvUgKrvnWZ/T/4iphOBftyVc9VrIwhAnsM+OFA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redux-saga/symbols": "^1.1.3",
|
"@redux-saga/symbols": "^1.2.1",
|
||||||
"@redux-saga/types": "^1.2.1"
|
"@redux-saga/types": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/symbols": {
|
"node_modules/@redux-saga/symbols": {
|
||||||
"version": "1.1.3",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.2.1.tgz",
|
||||||
"integrity": "sha512-hCx6ZvU4QAEUojETnX8EVg4ubNLBFl1Lps4j2tX7o45x/2qg37m3c6v+kSp8xjDJY+2tJw4QB3j8o8dsl1FDXg==",
|
"integrity": "sha512-3dh+uDvpBXi7EUp/eO+N7eFM4xKaU4yuGBXc50KnZGzIrR/vlvkTFQsX13zsY8PB6sCFYAgROfPSRUj8331QSA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@redux-saga/types": {
|
"node_modules/@redux-saga/types": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.3.1.tgz",
|
||||||
"integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==",
|
"integrity": "sha512-YRCrJdhQLobGIQ8Cj1sta3nn6DrZDTSUnrIYhS2e5V590BmfVDleKoAquclAiKSBKWJwmuXTb+b4BL6rSHnahw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
@@ -8686,12 +8764,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.13",
|
"version": "18.3.26",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
||||||
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -8787,6 +8866,12 @@
|
|||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/warning": {
|
"node_modules/@types/warning": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||||
@@ -23878,6 +23963,19 @@
|
|||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-error-boundary": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-error-overlay": {
|
"node_modules/react-error-overlay": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.1.0.tgz",
|
||||||
@@ -24039,16 +24137,6 @@
|
|||||||
"tslib": "2"
|
"tslib": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-intl/node_modules/@types/react": {
|
|
||||||
"version": "18.3.24",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
|
|
||||||
"integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/prop-types": "*",
|
|
||||||
"csstype": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@@ -24469,12 +24557,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redux-saga": {
|
"node_modules/redux-saga": {
|
||||||
"version": "1.3.0",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.4.2.tgz",
|
||||||
"integrity": "sha512-J9RvCeAZXSTAibFY0kGw6Iy4EdyDNW7k6Q+liwX+bsck7QVsU78zz8vpBRweEfANxnnlG/xGGeOvf6r8UXzNJQ==",
|
"integrity": "sha512-QLIn/q+7MX/B+MkGJ/K6R3//60eJ4QNy65eqPsJrfGezbxdh1Jx+37VRKE2K4PsJnNET5JufJtgWdT30WBa+6w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@redux-saga/core": "^1.3.0"
|
"@redux-saga/core": "^1.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redux-thunk": {
|
"node_modules/redux-thunk": {
|
||||||
@@ -27448,6 +27536,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util": {
|
"node_modules/util": {
|
||||||
"version": "0.12.5",
|
"version": "0.12.5",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
"@fortawesome/free-brands-svg-icons": "6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
"@fortawesome/free-solid-svg-icons": "6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "0.2.6",
|
"@fortawesome/react-fontawesome": "0.2.6",
|
||||||
|
"@openedx/frontend-plugin-framework": "^1.7.0",
|
||||||
"@openedx/paragon": "^23.4.2",
|
"@openedx/paragon": "^23.4.2",
|
||||||
"@optimizely/react-sdk": "^2.9.1",
|
"@optimizely/react-sdk": "^2.9.1",
|
||||||
"@redux-devtools/extension": "3.3.0",
|
"@redux-devtools/extension": "3.3.0",
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
"redux": "4.2.1",
|
"redux": "4.2.1",
|
||||||
"redux-logger": "3.0.6",
|
"redux-logger": "3.0.6",
|
||||||
"redux-mock-store": "1.5.5",
|
"redux-mock-store": "1.5.5",
|
||||||
"redux-saga": "1.3.0",
|
"redux-saga": "1.4.2",
|
||||||
"redux-thunk": "2.4.2",
|
"redux-thunk": "2.4.2",
|
||||||
"regenerator-runtime": "0.14.1",
|
"regenerator-runtime": "0.14.1",
|
||||||
"reselect": "5.1.1",
|
"reselect": "5.1.1",
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import {
|
||||||
import { connect } from 'react-redux';
|
useCallback, useEffect, useMemo, useState,
|
||||||
|
} from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||||
import {
|
import { Form, StatefulButton } from '@openedx/paragon';
|
||||||
Form, StatefulButton,
|
|
||||||
} from '@openedx/paragon';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import Skeleton from 'react-loading-skeleton';
|
import Skeleton from 'react-loading-skeleton';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import AccountActivationMessage from './AccountActivationMessage';
|
|
||||||
import {
|
|
||||||
backupLoginFormBegin,
|
|
||||||
dismissPasswordResetBanner,
|
|
||||||
loginRequest,
|
|
||||||
} from './data/actions';
|
|
||||||
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
|
||||||
import LoginFailureMessage from './LoginFailure';
|
|
||||||
import messages from './messages';
|
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InstitutionLogistration,
|
InstitutionLogistration,
|
||||||
@@ -28,13 +19,12 @@ import {
|
|||||||
RedirectLogistration,
|
RedirectLogistration,
|
||||||
ThirdPartyAuthAlert,
|
ThirdPartyAuthAlert,
|
||||||
} from '../common-components';
|
} from '../common-components';
|
||||||
|
import AccountActivationMessage from './AccountActivationMessage';
|
||||||
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
||||||
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
|
||||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||||
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
import ThirdPartyAuth from '../common-components/ThirdPartyAuth';
|
||||||
import {
|
import { PENDING_STATE, RESET_PAGE } from '../data/constants';
|
||||||
DEFAULT_STATE, PENDING_STATE, RESET_PAGE,
|
|
||||||
} from '../data/constants';
|
|
||||||
import {
|
import {
|
||||||
getActivationStatus,
|
getActivationStatus,
|
||||||
getAllPossibleQueryParams,
|
getAllPossibleQueryParams,
|
||||||
@@ -43,37 +33,57 @@ import {
|
|||||||
updatePathWithQueryParams,
|
updatePathWithQueryParams,
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
import ResetPasswordSuccess from '../reset-password/ResetPasswordSuccess';
|
||||||
|
import { backupLoginFormBegin, dismissPasswordResetBanner, loginRequest } from './data/actions';
|
||||||
|
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
||||||
|
import LoginFailureMessage from './LoginFailure';
|
||||||
|
import messages from './messages';
|
||||||
|
|
||||||
const LoginPage = (props) => {
|
const LoginPage = ({
|
||||||
|
institutionLogin,
|
||||||
|
handleInstitutionLogin,
|
||||||
|
}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const backupFormState = useCallback((data) => dispatch(backupLoginFormBegin(data)), [dispatch]);
|
||||||
|
const getTPADataFromBackend = useCallback(() => dispatch(getThirdPartyAuthContext()), [dispatch]);
|
||||||
const {
|
const {
|
||||||
backedUpFormData,
|
backedUpFormData,
|
||||||
loginErrorCode,
|
loginErrorCode,
|
||||||
loginErrorContext,
|
loginErrorContext,
|
||||||
loginResult,
|
loginResult,
|
||||||
shouldBackupState,
|
shouldBackupState,
|
||||||
thirdPartyAuthContext: {
|
|
||||||
providers,
|
|
||||||
currentProvider,
|
|
||||||
secondaryProviders,
|
|
||||||
finishAuthUrl,
|
|
||||||
platformName,
|
|
||||||
errorMessage: thirdPartyErrorMessage,
|
|
||||||
},
|
|
||||||
thirdPartyAuthApiStatus,
|
|
||||||
institutionLogin,
|
|
||||||
showResetPasswordSuccessBanner,
|
showResetPasswordSuccessBanner,
|
||||||
submitState,
|
submitState,
|
||||||
// Actions
|
thirdPartyAuthContext,
|
||||||
backupFormState,
|
thirdPartyAuthApiStatus,
|
||||||
handleInstitutionLogin,
|
} = useSelector((state) => ({
|
||||||
getTPADataFromBackend,
|
backedUpFormData: state.login.loginFormData,
|
||||||
} = props;
|
loginErrorCode: state.login.loginErrorCode,
|
||||||
|
loginErrorContext: state.login.loginErrorContext,
|
||||||
|
loginResult: state.login.loginResult,
|
||||||
|
shouldBackupState: state.login.shouldBackupState,
|
||||||
|
showResetPasswordSuccessBanner: state.login.showResetPasswordSuccessBanner,
|
||||||
|
submitState: state.login.submitState,
|
||||||
|
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
|
||||||
|
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
||||||
|
}));
|
||||||
|
const {
|
||||||
|
providers,
|
||||||
|
currentProvider,
|
||||||
|
secondaryProviders,
|
||||||
|
finishAuthUrl,
|
||||||
|
platformName,
|
||||||
|
errorMessage: thirdPartyErrorMessage,
|
||||||
|
} = thirdPartyAuthContext;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const activationMsgType = getActivationStatus();
|
const activationMsgType = getActivationStatus();
|
||||||
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
||||||
|
|
||||||
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
||||||
const [errorCode, setErrorCode] = useState({ type: '', count: 0, context: {} });
|
const [errorCode, setErrorCode] = useState({
|
||||||
|
type: '',
|
||||||
|
count: 0,
|
||||||
|
context: {},
|
||||||
|
});
|
||||||
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||||
const tpaHint = getTpaHint();
|
const tpaHint = getTpaHint();
|
||||||
|
|
||||||
@@ -87,7 +97,7 @@ const LoginPage = (props) => {
|
|||||||
payload.tpa_hint = tpaHint;
|
payload.tpa_hint = tpaHint;
|
||||||
}
|
}
|
||||||
getTPADataFromBackend(payload);
|
getTPADataFromBackend(payload);
|
||||||
}, [getTPADataFromBackend, queryParams, tpaHint]);
|
}, [queryParams, tpaHint, getTPADataFromBackend]);
|
||||||
/**
|
/**
|
||||||
* Backup the login form in redux when login page is toggled.
|
* Backup the login form in redux when login page is toggled.
|
||||||
*/
|
*/
|
||||||
@@ -98,7 +108,7 @@ const LoginPage = (props) => {
|
|||||||
errors: { ...errors },
|
errors: { ...errors },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [shouldBackupState, formFields, errors, backupFormState]);
|
}, [backupFormState, shouldBackupState, formFields, errors]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loginErrorCode) {
|
if (loginErrorCode) {
|
||||||
@@ -123,7 +133,10 @@ const LoginPage = (props) => {
|
|||||||
}, [thirdPartyErrorMessage]);
|
}, [thirdPartyErrorMessage]);
|
||||||
|
|
||||||
const validateFormFields = (payload) => {
|
const validateFormFields = (payload) => {
|
||||||
const { emailOrUsername, password } = payload;
|
const {
|
||||||
|
emailOrUsername,
|
||||||
|
password,
|
||||||
|
} = payload;
|
||||||
const fieldErrors = { ...errors };
|
const fieldErrors = { ...errors };
|
||||||
|
|
||||||
if (emailOrUsername === '') {
|
if (emailOrUsername === '') {
|
||||||
@@ -141,14 +154,18 @@ const LoginPage = (props) => {
|
|||||||
const handleSubmit = (event) => {
|
const handleSubmit = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (showResetPasswordSuccessBanner) {
|
if (showResetPasswordSuccessBanner) {
|
||||||
props.dismissPasswordResetBanner();
|
dispatch(dismissPasswordResetBanner());
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = { ...formFields };
|
const formData = { ...formFields };
|
||||||
const validationErrors = validateFormFields(formData);
|
const validationErrors = validateFormFields(formData);
|
||||||
if (validationErrors.emailOrUsername || validationErrors.password) {
|
if (validationErrors.emailOrUsername || validationErrors.password) {
|
||||||
setErrors({ ...validationErrors });
|
setErrors({ ...validationErrors });
|
||||||
setErrorCode(prevState => ({ type: INVALID_FORM, count: prevState.count + 1, context: {} }));
|
setErrorCode(prevState => ({
|
||||||
|
type: INVALID_FORM,
|
||||||
|
count: prevState.count + 1,
|
||||||
|
context: {},
|
||||||
|
}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,23 +175,35 @@ const LoginPage = (props) => {
|
|||||||
password: formData.password,
|
password: formData.password,
|
||||||
...queryParams,
|
...queryParams,
|
||||||
};
|
};
|
||||||
props.loginRequest(payload);
|
dispatch(loginRequest(payload));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnChange = (event) => {
|
const handleOnChange = (event) => {
|
||||||
const { name, value } = event.target;
|
const {
|
||||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
name,
|
||||||
|
value,
|
||||||
|
} = event.target;
|
||||||
|
setFormFields(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnFocus = (event) => {
|
const handleOnFocus = (event) => {
|
||||||
const { name } = event.target;
|
const { name } = event.target;
|
||||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
setErrors(prevErrors => ({
|
||||||
|
...prevErrors,
|
||||||
|
[name]: '',
|
||||||
|
}));
|
||||||
};
|
};
|
||||||
const trackForgotPasswordLinkClick = () => {
|
const trackForgotPasswordLinkClick = () => {
|
||||||
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
|
||||||
};
|
};
|
||||||
|
|
||||||
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
const {
|
||||||
|
provider,
|
||||||
|
skipHintedLogin,
|
||||||
|
} = getTpaProvider(tpaHint, providers, secondaryProviders);
|
||||||
|
|
||||||
if (tpaHint) {
|
if (tpaHint) {
|
||||||
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
||||||
@@ -281,88 +310,9 @@ const LoginPage = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const loginPageState = state.login;
|
|
||||||
return {
|
|
||||||
backedUpFormData: loginPageState.loginFormData,
|
|
||||||
loginErrorCode: loginPageState.loginErrorCode,
|
|
||||||
loginErrorContext: loginPageState.loginErrorContext,
|
|
||||||
loginResult: loginPageState.loginResult,
|
|
||||||
shouldBackupState: loginPageState.shouldBackupState,
|
|
||||||
showResetPasswordSuccessBanner: loginPageState.showResetPasswordSuccessBanner,
|
|
||||||
submitState: loginPageState.submitState,
|
|
||||||
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
|
|
||||||
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
LoginPage.propTypes = {
|
LoginPage.propTypes = {
|
||||||
backedUpFormData: PropTypes.shape({
|
|
||||||
formFields: PropTypes.shape({}),
|
|
||||||
errors: PropTypes.shape({}),
|
|
||||||
}),
|
|
||||||
loginErrorCode: PropTypes.string,
|
|
||||||
loginErrorContext: PropTypes.shape({
|
|
||||||
email: PropTypes.string,
|
|
||||||
redirectUrl: PropTypes.string,
|
|
||||||
context: PropTypes.shape({}),
|
|
||||||
}),
|
|
||||||
loginResult: PropTypes.shape({
|
|
||||||
redirectUrl: PropTypes.string,
|
|
||||||
success: PropTypes.bool,
|
|
||||||
}),
|
|
||||||
shouldBackupState: PropTypes.bool,
|
|
||||||
showResetPasswordSuccessBanner: PropTypes.bool,
|
|
||||||
submitState: PropTypes.string,
|
|
||||||
thirdPartyAuthApiStatus: PropTypes.string,
|
|
||||||
institutionLogin: PropTypes.bool.isRequired,
|
institutionLogin: PropTypes.bool.isRequired,
|
||||||
thirdPartyAuthContext: PropTypes.shape({
|
|
||||||
currentProvider: PropTypes.string,
|
|
||||||
errorMessage: PropTypes.string,
|
|
||||||
platformName: PropTypes.string,
|
|
||||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
finishAuthUrl: PropTypes.string,
|
|
||||||
}),
|
|
||||||
// Actions
|
|
||||||
backupFormState: PropTypes.func.isRequired,
|
|
||||||
dismissPasswordResetBanner: PropTypes.func.isRequired,
|
|
||||||
loginRequest: PropTypes.func.isRequired,
|
|
||||||
getTPADataFromBackend: PropTypes.func.isRequired,
|
|
||||||
handleInstitutionLogin: PropTypes.func.isRequired,
|
handleInstitutionLogin: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
LoginPage.defaultProps = {
|
export default LoginPage;
|
||||||
backedUpFormData: {
|
|
||||||
formFields: {
|
|
||||||
emailOrUsername: '', password: '',
|
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
emailOrUsername: '', password: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
loginErrorCode: null,
|
|
||||||
loginErrorContext: {},
|
|
||||||
loginResult: {},
|
|
||||||
shouldBackupState: false,
|
|
||||||
showResetPasswordSuccessBanner: false,
|
|
||||||
submitState: DEFAULT_STATE,
|
|
||||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
|
||||||
thirdPartyAuthContext: {
|
|
||||||
currentProvider: null,
|
|
||||||
errorMessage: null,
|
|
||||||
finishAuthUrl: null,
|
|
||||||
providers: [],
|
|
||||||
secondaryProviders: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
{
|
|
||||||
backupFormState: backupLoginFormBegin,
|
|
||||||
dismissPasswordResetBanner,
|
|
||||||
loginRequest,
|
|
||||||
getTPADataFromBackend: getThirdPartyAuthContext,
|
|
||||||
},
|
|
||||||
)(LoginPage);
|
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ describe('LoginPage', () => {
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
login: {
|
login: {
|
||||||
loginResult: { success: false, redirectUrl: '' },
|
loginResult: { success: false, redirectUrl: '' },
|
||||||
|
loginFormData: {
|
||||||
|
formFields: {
|
||||||
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
commonComponents: {
|
commonComponents: {
|
||||||
thirdPartyAuthApiStatus: null,
|
thirdPartyAuthApiStatus: null,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { getConfig } from '@edx/frontend-platform';
|
import { getConfig } from '@edx/frontend-platform';
|
||||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
@@ -24,16 +24,20 @@ import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
|||||||
import {
|
import {
|
||||||
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
getTpaHint, getTpaProvider, updatePathWithQueryParams,
|
||||||
} from '../data/utils';
|
} from '../data/utils';
|
||||||
import { LoginPage } from '../login';
|
|
||||||
import { backupLoginForm } from '../login/data/actions';
|
import { backupLoginForm } from '../login/data/actions';
|
||||||
|
import LoginComponentSlot from '../plugin-slots/LoginComponentSlot';
|
||||||
import { RegistrationPage } from '../register';
|
import { RegistrationPage } from '../register';
|
||||||
import { backupRegistrationForm } from '../register/data/actions';
|
import { backupRegistrationForm } from '../register/data/actions';
|
||||||
|
|
||||||
const Logistration = (props) => {
|
const Logistration = ({
|
||||||
const { selectedPage, tpaProviders } = props;
|
selectedPage,
|
||||||
|
}) => {
|
||||||
const tpaHint = getTpaHint();
|
const tpaHint = getTpaHint();
|
||||||
|
const tpaProviders = useSelector(tpaProvidersSelector);
|
||||||
|
const dispatch = useDispatch();
|
||||||
const {
|
const {
|
||||||
providers, secondaryProviders,
|
providers,
|
||||||
|
secondaryProviders,
|
||||||
} = tpaProviders;
|
} = tpaProviders;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [institutionLogin, setInstitutionLogin] = useState(false);
|
const [institutionLogin, setInstitutionLogin] = useState(false);
|
||||||
@@ -45,7 +49,8 @@ const Logistration = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const authService = getAuthService();
|
const authService = getAuthService();
|
||||||
if (authService) {
|
if (authService) {
|
||||||
authService.getCsrfTokenService().getCsrfToken(getConfig().LMS_BASE_URL);
|
authService.getCsrfTokenService()
|
||||||
|
.getCsrfToken(getConfig().LMS_BASE_URL);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,11 +76,11 @@ const Logistration = (props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
||||||
props.clearThirdPartyAuthContextErrorMessage();
|
dispatch(clearThirdPartyAuthContextErrorMessage());
|
||||||
if (tabKey === LOGIN_PAGE) {
|
if (tabKey === LOGIN_PAGE) {
|
||||||
props.backupRegistrationForm();
|
dispatch(backupRegistrationForm());
|
||||||
} else if (tabKey === REGISTER_PAGE) {
|
} else if (tabKey === REGISTER_PAGE) {
|
||||||
props.backupLoginForm();
|
dispatch(backupLoginForm());
|
||||||
}
|
}
|
||||||
setKey(tabKey);
|
setKey(tabKey);
|
||||||
};
|
};
|
||||||
@@ -111,7 +116,10 @@ const Logistration = (props) => {
|
|||||||
{!institutionLogin && (
|
{!institutionLogin && (
|
||||||
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
|
<h3 className="mb-4.5">{formatMessage(messages['logistration.sign.in'])}</h3>
|
||||||
)}
|
)}
|
||||||
<LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
|
<LoginComponentSlot
|
||||||
|
institutionLogin={institutionLogin}
|
||||||
|
handleInstitutionLogin={handleInstitutionLogin}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@@ -124,12 +132,16 @@ const Logistration = (props) => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
)
|
)
|
||||||
: (!isValidTpaHint() && !hideRegistrationLink && (
|
: (!isValidTpaHint() && !hideRegistrationLink && (
|
||||||
<Tabs defaultActiveKey={selectedPage} id="controlled-tab" onSelect={(tabKey) => handleOnSelect(tabKey, selectedPage)}>
|
<Tabs
|
||||||
|
defaultActiveKey={selectedPage}
|
||||||
|
id="controlled-tab"
|
||||||
|
onSelect={(tabKey) => handleOnSelect(tabKey, selectedPage)}
|
||||||
|
>
|
||||||
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
|
<Tab title={formatMessage(messages['logistration.register'])} eventKey={REGISTER_PAGE} />
|
||||||
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
|
<Tab title={formatMessage(messages['logistration.sign.in'])} eventKey={LOGIN_PAGE} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
))}
|
))}
|
||||||
{ key && (
|
{key && (
|
||||||
<Navigate to={updatePathWithQueryParams(key)} replace />
|
<Navigate to={updatePathWithQueryParams(key)} replace />
|
||||||
)}
|
)}
|
||||||
<div id="main-content" className="main-content">
|
<div id="main-content" className="main-content">
|
||||||
@@ -139,7 +151,12 @@ const Logistration = (props) => {
|
|||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
{selectedPage === LOGIN_PAGE
|
{selectedPage === LOGIN_PAGE
|
||||||
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
|
? (
|
||||||
|
<LoginComponentSlot
|
||||||
|
institutionLogin={institutionLogin}
|
||||||
|
handleInstitutionLogin={handleInstitutionLogin}
|
||||||
|
/>
|
||||||
|
)
|
||||||
: (
|
: (
|
||||||
<RegistrationPage
|
<RegistrationPage
|
||||||
institutionLogin={institutionLogin}
|
institutionLogin={institutionLogin}
|
||||||
@@ -156,35 +173,10 @@ const Logistration = (props) => {
|
|||||||
|
|
||||||
Logistration.propTypes = {
|
Logistration.propTypes = {
|
||||||
selectedPage: PropTypes.string,
|
selectedPage: PropTypes.string,
|
||||||
backupLoginForm: PropTypes.func.isRequired,
|
|
||||||
backupRegistrationForm: PropTypes.func.isRequired,
|
|
||||||
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
|
|
||||||
tpaProviders: PropTypes.shape({
|
|
||||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
Logistration.defaultProps = {
|
|
||||||
tpaProviders: {
|
|
||||||
providers: [],
|
|
||||||
secondaryProviders: [],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Logistration.defaultProps = {
|
Logistration.defaultProps = {
|
||||||
selectedPage: REGISTER_PAGE,
|
selectedPage: REGISTER_PAGE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
export default Logistration;
|
||||||
tpaProviders: tpaProvidersSelector(state),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
{
|
|
||||||
backupLoginForm,
|
|
||||||
backupRegistrationForm,
|
|
||||||
clearThirdPartyAuthContextErrorMessage,
|
|
||||||
},
|
|
||||||
)(Logistration);
|
|
||||||
|
|||||||
@@ -48,16 +48,26 @@ describe('Logistration', () => {
|
|||||||
marketingEmailsOptIn: true,
|
marketingEmailsOptIn: true,
|
||||||
},
|
},
|
||||||
formFields: {
|
formFields: {
|
||||||
name: '', email: '', username: '', password: '',
|
name: '',
|
||||||
|
email: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
},
|
},
|
||||||
emailSuggestion: {
|
emailSuggestion: {
|
||||||
suggestion: '', type: '',
|
suggestion: '',
|
||||||
|
type: '',
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
name: '', email: '', username: '', password: '',
|
name: '',
|
||||||
|
email: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
registrationResult: { success: false, redirectUrl: '' },
|
registrationResult: {
|
||||||
|
success: false,
|
||||||
|
redirectUrl: '',
|
||||||
|
},
|
||||||
registrationError: {},
|
registrationError: {},
|
||||||
usernameSuggestions: [],
|
usernameSuggestions: [],
|
||||||
validationApiRateLimited: false,
|
validationApiRateLimited: false,
|
||||||
@@ -69,7 +79,18 @@ describe('Logistration', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
login: {
|
login: {
|
||||||
loginResult: { success: false, redirectUrl: '' },
|
loginResult: {
|
||||||
|
success: false,
|
||||||
|
redirectUrl: '',
|
||||||
|
},
|
||||||
|
loginFormData: {
|
||||||
|
formFields: {
|
||||||
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
emailOrUsername: '', password: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
47
src/plugin-slots/LoginComponentSlot/README.md
Normal file
47
src/plugin-slots/LoginComponentSlot/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Login Component Slot
|
||||||
|
|
||||||
|
### Slot ID: `org.openedx.frontend.authn.login_component.v1`
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
This slot is used to replace/modify/hide the login component.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
### Default content
|
||||||
|

|
||||||
|
|
||||||
|
### With a prepended message
|
||||||
|

|
||||||
|
|
||||||
|
The following `env.config.jsx` will add a message before the login component.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
|
||||||
|
|
||||||
|
// Load environment variables from .env file
|
||||||
|
const config = {
|
||||||
|
...process.env,
|
||||||
|
pluginSlots: {
|
||||||
|
'org.openedx.frontend.authn.login_component.v1': {
|
||||||
|
keepDefault: true,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
op: PLUGIN_OPERATIONS.Insert,
|
||||||
|
widget: {
|
||||||
|
id: 'test_plugin',
|
||||||
|
type: DIRECT_PLUGIN,
|
||||||
|
priority: 1,
|
||||||
|
RenderWidget: () => (
|
||||||
|
<h2>You're logging into TEST Instance.</h2>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
|
||||||
|
```
|
||||||
BIN
src/plugin-slots/LoginComponentSlot/component_with_prefix.png
Normal file
BIN
src/plugin-slots/LoginComponentSlot/component_with_prefix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
src/plugin-slots/LoginComponentSlot/default_component.png
Normal file
BIN
src/plugin-slots/LoginComponentSlot/default_component.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
31
src/plugin-slots/LoginComponentSlot/index.jsx
Normal file
31
src/plugin-slots/LoginComponentSlot/index.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { PluginSlot } from '@openedx/frontend-plugin-framework';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import LoginPage from '../../login/LoginPage';
|
||||||
|
|
||||||
|
const LoginComponentSlot = ({
|
||||||
|
institutionLogin,
|
||||||
|
handleInstitutionLogin,
|
||||||
|
}) => (
|
||||||
|
<PluginSlot
|
||||||
|
id="org.openedx.frontend.authn.login_component.v1"
|
||||||
|
pluginProps={{
|
||||||
|
isInstitutionLogin: institutionLogin,
|
||||||
|
setInstitutionLogin: handleInstitutionLogin,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoginPage
|
||||||
|
institutionLogin={institutionLogin}
|
||||||
|
handleInstitutionLogin={handleInstitutionLogin}
|
||||||
|
/>
|
||||||
|
</PluginSlot>
|
||||||
|
);
|
||||||
|
|
||||||
|
LoginComponentSlot.propTypes = {
|
||||||
|
institutionLogin: PropTypes.bool,
|
||||||
|
handleInstitutionLogin: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginComponentSlot;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { renderHook } from '@testing-library/react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { render } from '@testing-library/react';
|
||||||
import algoliasearchHelper from 'algoliasearch-helper';
|
import algoliasearchHelper from 'algoliasearch-helper';
|
||||||
|
|
||||||
import mockedRecommendedProducts from './mockedData';
|
import mockedRecommendedProducts from './mockedData';
|
||||||
import CreateAlgoliaSearchHelperMock from './test_utils/test_utils';
|
|
||||||
import isOneTrustFunctionalCookieEnabled from '../../../data/oneTrust';
|
import isOneTrustFunctionalCookieEnabled from '../../../data/oneTrust';
|
||||||
import useAlgoliaRecommendations from '../hooks/useAlgoliaRecommendations';
|
import useAlgoliaRecommendations from '../hooks/useAlgoliaRecommendations';
|
||||||
|
import CreateAlgoliaSearchHelperMock from './test_utils/test_utils';
|
||||||
|
|
||||||
jest.mock('algoliasearch-helper');
|
jest.mock('algoliasearch-helper');
|
||||||
|
|
||||||
@@ -17,6 +19,23 @@ jest.mock('../../../data/algolia', () => ({
|
|||||||
|
|
||||||
jest.mock('../algoliaResultsParser', () => jest.fn((course) => course));
|
jest.mock('../algoliaResultsParser', () => jest.fn((course) => course));
|
||||||
|
|
||||||
|
const renderHook = (hookCallback) => {
|
||||||
|
const result = {
|
||||||
|
current: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Component = () => {
|
||||||
|
const val = hookCallback();
|
||||||
|
React.useEffect(() => {
|
||||||
|
result.current = val;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<Component />);
|
||||||
|
return { result };
|
||||||
|
};
|
||||||
|
|
||||||
describe('useAlgoliaRecommendations Tests', () => {
|
describe('useAlgoliaRecommendations Tests', () => {
|
||||||
const MockSearchHelperWithData = new CreateAlgoliaSearchHelperMock(mockedRecommendedProducts);
|
const MockSearchHelperWithData = new CreateAlgoliaSearchHelperMock(mockedRecommendedProducts);
|
||||||
const MockSearchHelperWithoutData = new CreateAlgoliaSearchHelperMock();
|
const MockSearchHelperWithoutData = new CreateAlgoliaSearchHelperMock();
|
||||||
@@ -28,8 +47,10 @@ describe('useAlgoliaRecommendations Tests', () => {
|
|||||||
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.recommendations).toEqual(mockedRecommendedProducts);
|
expect(result.current.recommendations)
|
||||||
expect(result.current.isLoading).toBe(false);
|
.toEqual(mockedRecommendedProducts);
|
||||||
|
expect(result.current.isLoading)
|
||||||
|
.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fetch recommendations if functional cookies are not set', async () => {
|
it('should not fetch recommendations if functional cookies are not set', async () => {
|
||||||
@@ -39,8 +60,10 @@ describe('useAlgoliaRecommendations Tests', () => {
|
|||||||
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.recommendations).toEqual([]);
|
expect(result.current.recommendations)
|
||||||
expect(result.current.isLoading).toBe(false);
|
.toEqual([]);
|
||||||
|
expect(result.current.isLoading)
|
||||||
|
.toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return empty list if no recommendations returned from Algolia', async () => {
|
it('should return empty list if no recommendations returned from Algolia', async () => {
|
||||||
@@ -50,7 +73,9 @@ describe('useAlgoliaRecommendations Tests', () => {
|
|||||||
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
() => useAlgoliaRecommendations('PK', 'Introductory'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result.current.recommendations).toEqual([]);
|
expect(result.current.recommendations)
|
||||||
expect(result.current.isLoading).toBe(false);
|
.toEqual([]);
|
||||||
|
expect(result.current.isLoading)
|
||||||
|
.toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user