Compare commits

...

43 Commits

Author SHA1 Message Date
Matthew Piatetsky
ffe10e56f5 feat: add user id parameter to progress page 2021-06-23 17:19:46 -04:00
Matthew Piatetsky
15027ab69e feat: change color of start/resume course button 2021-06-21 10:50:25 -04:00
Albert (AJ) St. Aubin
1b84930a84 feature: Added notification and link to get to a users PDF cert.
This feature will allow users with downloadable PDF certificates to see
the certificate status alert and then access their certificate on the
Course Outline page. This should only show once a learner has earned a
certificate and that certificate is available.
2021-06-17 07:33:33 -04:00
Michael Terry
99185a9b8a fix: guard against courseGoals being undefined (#489)
No idea why this would happen honestly - it looks always defined
from the backend, and api.js doesn't transorm it. But we are seeing
JS errors related to it. So trying this as a first pass.

AA-848
2021-06-15 13:13:38 -04:00
Carla Duarte
e9c3a6bc5e fix: update assignment policy logic (#483) 2021-06-14 11:22:57 -04:00
Sagirov Evgeniy
5a30cddd32 feat: Added 'allow_proctoring_opt_out' attribute for SequenceMetadata API. (#485) 2021-06-14 10:44:37 -04:00
Ihor Romaniuk
432cb669f5 feat: add temporary flag for enabling/disabling proctored exams (#464) 2021-06-14 10:42:53 -04:00
Matthew Piatetsky
26e1eb64c5 fix: ensure clicking the bookmarks button doesn't break the unitHasLoaded property (#481) 2021-06-14 09:36:15 -04:00
edX Transifex Bot
30e0e3b8f4 fix(i18n): update translations 2021-06-14 02:08:38 +05:00
Carla Duarte
46e459aaf3 fix: update progress tab assignment policy logic (#482) 2021-06-11 12:05:40 -04:00
Carla Duarte
6949fa8201 fix: updating progress tab to better respect showGrades field (#480) 2021-06-11 09:42:06 -04:00
Albert (AJ) St. Aubin
fab2da4586 feature: Improve the Certificate Alerts in the outline to support new
statuses

[MICROBA-678]

These changes refactor the CertificateAvailableAlert and add new
features to it to support more status alerts for certificates. It
attempts to do so in an iterative manner so that new/updated alerts can
be included over time.
2021-06-10 13:03:12 -04:00
Kristin Aoki
6a402c50ea fix: Scroll page when html anchor tag clicked
This PR adds a listener check to messages. Previously the anchor tags that were set to scroll on the page to another element would open the link outside of the iframe and redirect the parent page. As a result, users would have to have to click the back arrow to navigate back to the course and continue the unit. Now when the listener receives an object with offset in the event.data, the page will scroll to the location provided by offset. The offset is only received when a user clicks on an anchor tag in the unit iframe that focuses on another element on the page. This change will impact the Learner.

Jira issue: TNL-8312
2021-06-09 13:18:46 -04:00
Zachary Hancock
bfeb8c70c0 fix: pin special exams library (#479) 2021-06-09 11:09:05 -04:00
David Ormsbee
cf61c7a747 fix: display Unit titles as <h1> (TNL-8387)
Unit titles were being written to the page as <h2> because the old
courseware experience reserved <h1> for wrapping the header logo link.
We've since determined that this is not a best practice, and the new
courseware MFE in this repo no longer uses a <h1> for that purpose, but
the Unit title was never promoted from <h2> to <h1> until this commit.

Course teams have traditionally been permitted to use <h3>-<h6> in their
content. Making this change does mean that there will now be a gap with
some content, where we skip from <h1> to <h3>. For the short term, we
are NOT recommending course teams use <h2>, until we have a better
chance to evaluate whether that heading should remain reserved for
platform-level use.
2021-06-07 13:49:58 -04:00
Adeel Ehsan
a003059c8f Account activation pop up added: (#474)
VAN-435
2021-06-07 10:33:59 +05:00
Adeel Ehsan
854010ba52 Revert "Account activation pop up added: (#425)" (#473)
This reverts commit 07b82b1d87.
2021-06-05 01:14:52 +05:00
Adeel Ehsan
07b82b1d87 Account activation pop up added: (#425)
VAN-435
2021-06-04 20:13:12 +05:00
Bianca Severino
5c204ad0f9 feat: add honor code component (#465)
This component blocks access to graded units when
the user is required to sign the integrity agreement for
the course. Once signed, it will not appear for the course
again.
2021-06-04 09:06:32 -04:00
Matthew Piatetsky
5bfca28450 feat: update streak discount coupon expiration date (#471) 2021-06-02 17:12:56 -04:00
Carla Duarte
a36da4cd84 AA-807: progress tab eventing (#470) 2021-06-02 14:31:13 -04:00
Diane Kaplan
f08a23ecf9 feat: remove first purchase discount banner from course home (REV-2253) 2021-06-01 09:12:28 -04:00
edX Transifex Bot
3432b0c73b fix(i18n): update translations 2021-05-29 02:00:46 +05:00
edX Transifex Bot
c1c3d5c68f fix(i18n): update translations 2021-05-29 00:03:12 +05:00
julianajlk
2a52534442 fix: gated content banner discount display (#466)
REV-2232
2021-05-28 13:38:49 -04:00
Carla Duarte
519cf27c4e AA-813: fix progress tab related links (#461) 2021-05-26 12:01:18 -04:00
Carla Duarte
9d07f26f13 AA-807: upgrade deadline passed on progress tab (#463) 2021-05-26 12:01:12 -04:00
Albert (AJ) St. Aubin
fdfb60bee8 feat: Updating the messages for certificate availability.
[MICROBA-678]
2021-05-26 07:23:48 -04:00
Dillon Dumesnil
75c9e93241 refactor: Update the defaults for our .env files (#459)
Nulls can provide undesired behavior so we want to switch to empty strings
2021-05-24 09:08:48 -07:00
Vladas Tamoshaitis
a5ba5655b6 feet: [BD-26] Add support for special exams (#435)
* feat: add packages dir to .gitignore

* Investigate exam redirect (#2)

* feat: remove exam redirect

* feat: take control over exam instructions

* refactor: use fedx code structure

* fix: remove debug logging, remove redirect check

Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>

* Add state and reducer for check microfrontend_special_exams waffle flag (#4)

* feat: add state and reducer for check microfrontend_special_exams waffle flag

* fix: rename special exams enabled flag

* fix: rename reducer for setting special exams enabled flag

* refactor: timer feature

* feat(tests): extend tests + fix failing ones, fix quality

* fix: revert removing package lock file

Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>

* fix: naming of waffle flag helpers to reflect relation with mfe

* fix: change naming of the waffle flag

* fix: revert remove package lock file

* feat: switch to @edx npm package

* fix: Remove redundant references from .gitignore

* fix: add is_mfe_special_exams_enabled to courseMetadata.factory.js

* fix: fix tests for 'Sequence' content wrapped in 'SequenceExamWrapper'

Co-authored-by: Sagirov Eugeniy <sagirov19@gmail.com>
Co-authored-by: Vladas Tamoshaitis <vladas.tamoshaitis@raccoongang.com>
Co-authored-by: Sagirov Evgeniy <34642612+UvgenGen@users.noreply.github.com>
Co-authored-by: Igor Degtiarov <igor.degtiarov@raccoongang.com>
2021-05-24 08:44:01 -04:00
edX Transifex Bot
46056a0c53 fix(i18n): update translations 2021-05-24 02:08:01 +05:00
Carla Duarte
05b2439ff6 AA-816: update grade summary calculations (#456) 2021-05-19 15:44:49 -04:00
Carla Duarte
663bf7562b AA-814: course grade footer fix (#455) 2021-05-19 13:44:28 -04:00
Renovate Bot
58e29d81be fix(deps): update dependency react-redux to v7.2.4 2021-05-17 20:40:00 +00:00
Renovate Bot
027eeb8a49 chore(deps): update dependency glob to v7.1.7 2021-05-17 20:05:07 +00:00
Renovate Bot
e6a4bcd833 chore(deps): update dependency codecov to v3.8.2 2021-05-17 19:09:59 +00:00
Julia Eskew
1da461e2de feat: Support classifying certain errors as ignored errors in an MFE. (#447)
Ignored errors are sent to New Relic as page actions instead of JS errors,
allowing those errors to still be tracked as occurring but without causing
unnecessary alerts.

Ignored errors are configured per-MFE, *not* globally.

Bump the frontend-platform version to 1.10.2.

Add IGNORED_ERROR_REGEX variable for use in development. The actual
production value will be read from the YAML in edx-internal.

TNL-7924
2021-05-17 13:43:01 -04:00
Matthew Piatetsky
3c07cab8c2 [AA-746] fix: add hour/minute to assignment due dates and update dates timeline with branding styles (#450)
* fix: add hour/minute to assignment due dates and update dates timeline with branding styles

* update badge styles
2021-05-17 12:39:34 -04:00
Carla Duarte
110088688a fix: grade range bug (#449) 2021-05-17 09:23:49 -04:00
Matthew Piatetsky
d8243d6ea8 fix: remove currency symbol to avoid double currency symbol with some upgrade buttons (#448) 2021-05-14 09:48:50 -04:00
Carla Duarte
b1fdbcccf3 AA-790: progress tab handle unenrolled/unauthenticated users (#445) 2021-05-13 15:07:19 -04:00
edX Transifex Bot
00205d4b1f fix(i18n): update translations 2021-05-12 22:29:14 +05:00
julianajlk
6100f3ac2e fix: i18n translations for UpgradeCard (#442)
REV-2126 Value Prop
2021-05-12 09:50:08 -04:00
83 changed files with 3513 additions and 1814 deletions

65
.env
View File

@@ -1,32 +1,35 @@
NODE_ENV='production' NODE_ENV='production'
ACCESS_TOKEN_COOKIE_NAME=null ACCESS_TOKEN_COOKIE_NAME=''
BASE_URL=null BASE_URL=''
CREDENTIALS_BASE_URL=null CREDENTIALS_BASE_URL=''
CSRF_TOKEN_API_PATH=null CSRF_TOKEN_API_PATH=''
DISCOVERY_API_BASE_URL=null DISCOVERY_API_BASE_URL=''
ENTERPRISE_LEARNER_PORTAL_HOSTNAME=null ECOMMERCE_BASE_URL=''
ECOMMERCE_BASE_URL=null ENTERPRISE_LEARNER_PORTAL_HOSTNAME=''
INSIGHTS_BASE_URL=null IGNORED_ERROR_REGEX=''
LANGUAGE_PREFERENCE_COOKIE_NAME=null INSIGHTS_BASE_URL=''
LMS_BASE_URL=null LANGUAGE_PREFERENCE_COOKIE_NAME=''
LOGIN_URL=null LMS_BASE_URL=''
LOGOUT_URL=null LOGIN_URL=''
LOGO_URL=null LOGOUT_URL=''
LOGO_TRADEMARK_URL=null LOGO_URL=''
LOGO_WHITE_URL=null LOGO_TRADEMARK_URL=''
FAVICON_URL=null LOGO_WHITE_URL=''
MARKETING_SITE_BASE_URL=null FAVICON_URL=''
ORDER_HISTORY_URL=null MARKETING_SITE_BASE_URL=''
REFRESH_ACCESS_TOKEN_ENDPOINT=null ORDER_HISTORY_URL=''
SEARCH_CATALOG_URL=null REFRESH_ACCESS_TOKEN_ENDPOINT=''
SEGMENT_KEY=null SEARCH_CATALOG_URL=''
SITE_NAME=null SEGMENT_KEY=''
SOCIAL_UTM_MILESTONE_CAMPAIGN=null SITE_NAME=''
STUDIO_BASE_URL=null SOCIAL_UTM_MILESTONE_CAMPAIGN=''
SUPPORT_URL=null STUDIO_BASE_URL=''
SUPPORT_URL_CALCULATOR_MATH=null SUPPORT_URL=''
SUPPORT_URL_ID_VERIFICATION=null SUPPORT_URL_CALCULATOR_MATH=''
SUPPORT_URL_VERIFIED_CERTIFICATE=null SUPPORT_URL_ID_VERIFICATION=''
TWITTER_HASHTAG=null SUPPORT_URL_VERIFIED_CERTIFICATE=''
TWITTER_URL=null TERMS_OF_SERVICE_URL=''
USER_INFO_COOKIE_NAME=null TWITTER_HASHTAG=''
TWITTER_URL=''
USER_INFO_COOKIE_NAME=''
SESSION_COOKIE_DOMAIN=''

View File

@@ -6,6 +6,7 @@ CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381' DISCOVERY_API_BASE_URL='http://localhost:18381'
ECOMMERCE_BASE_URL='http://localhost:18130' ECOMMERCE_BASE_URL='http://localhost:18130'
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734' ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
IGNORED_ERROR_REGEX=''
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference' LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000' LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login' LOGIN_URL='http://localhost:18000/login'
@@ -19,7 +20,7 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000 PORT=2000
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh' REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEARCH_CATALOG_URL='http://localhost:18000/courses' SEARCH_CATALOG_URL='http://localhost:18000/courses'
SEGMENT_KEY=null SEGMENT_KEY=''
SITE_NAME='edX' SITE_NAME='edX'
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone' SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
STUDIO_BASE_URL='http://localhost:18010' STUDIO_BASE_URL='http://localhost:18010'
@@ -27,6 +28,8 @@ SUPPORT_URL='https://support.edx.org'
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator' SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity' SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate' SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
TERMS_OF_SERVICE_URL='https://www.edx.org/edx-terms-service'
TWITTER_HASHTAG='myedxjourney' TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline' TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info' USER_INFO_COOKIE_NAME='edx-user-info'
SESSION_COOKIE_DOMAIN='localhost'

View File

@@ -6,6 +6,7 @@ CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DISCOVERY_API_BASE_URL='http://localhost:18381' DISCOVERY_API_BASE_URL='http://localhost:18381'
ECOMMERCE_BASE_URL='http://localhost:18130' ECOMMERCE_BASE_URL='http://localhost:18130'
ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734' ENTERPRISE_LEARNER_PORTAL_HOSTNAME='localhost:8734'
IGNORED_ERROR_REGEX=''
LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference' LANGUAGE_PREFERENCE_COOKIE_NAME='openedx-language-preference'
LMS_BASE_URL='http://localhost:18000' LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login' LOGIN_URL='http://localhost:18000/login'
@@ -19,7 +20,7 @@ ORDER_HISTORY_URL='http://localhost:1996/orders'
PORT=2000 PORT=2000
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh' REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
SEARCH_CATALOG_URL='http://localhost:18000/courses' SEARCH_CATALOG_URL='http://localhost:18000/courses'
SEGMENT_KEY=null SEGMENT_KEY=''
SITE_NAME='edX' SITE_NAME='edX'
SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone' SOCIAL_UTM_MILESTONE_CAMPAIGN='edxmilestone'
STUDIO_BASE_URL='http://localhost:18010' STUDIO_BASE_URL='http://localhost:18010'
@@ -27,6 +28,7 @@ SUPPORT_URL='https://support.edx.org'
SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator' SUPPORT_URL_CALCULATOR_MATH='https://support.edx.org/hc/en-us/articles/360000038428-Entering-math-expressions-in-assignments-or-the-calculator'
SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity' SUPPORT_URL_ID_VERIFICATION='https://support.edx.org/hc/en-us/articles/206503858-How-do-I-verify-my-identity'
SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate' SUPPORT_URL_VERIFIED_CERTIFICATE='https://support.edx.org/hc/en-us/articles/206502008-What-is-a-verified-certificate'
TERMS_OF_SERVICE_URL='https://www.edx.org/edx-terms-service'
TWITTER_HASHTAG='myedxjourney' TWITTER_HASHTAG='myedxjourney'
TWITTER_URL='https://twitter.com/edXOnline' TWITTER_URL='https://twitter.com/edXOnline'
USER_INFO_COOKIE_NAME='edx-user-info' USER_INFO_COOKIE_NAME='edx-user-info'

View File

@@ -1,4 +1,5 @@
coverage/* coverage/*
dist/ dist/
packages/
node_modules/ node_modules/
jest.config.js jest.config.js

2121
package-lock.json generated
View File

@@ -39,9 +39,9 @@
} }
}, },
"@babel/compat-data": { "@babel/compat-data": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz",
"integrity": "sha512-U/hshG5R+SIoW7HVWIdmy1cB7s3ki+r3FpyEZiCgpi4tFgPnX/vynY80ZGSASOIrUM6O7VxOgCZgdt7h97bUGg==", "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==",
"dev": true "dev": true
}, },
"@babel/core": { "@babel/core": {
@@ -92,12 +92,12 @@
} }
}, },
"@babel/generator": { "@babel/generator": {
"version": "7.12.15", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.1.tgz",
"integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", "integrity": "sha512-TMGhsXMXCP/O1WtQmZjpEYDhCYC9vFhayWZPJSZCGkPJgUqX0rF0wwtrYvnzVxIjcF80tkUertXVk5cwqi5cAQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13", "@babel/types": "^7.14.1",
"jsesc": "^2.5.1", "jsesc": "^2.5.1",
"source-map": "^0.5.0" "source-map": "^0.5.0"
}, },
@@ -130,34 +130,43 @@
} }
}, },
"@babel/helper-compilation-targets": { "@babel/helper-compilation-targets": {
"version": "7.12.13", "version": "7.13.16",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz",
"integrity": "sha512-dXof20y/6wB5HnLOGyLh/gobsMvDNoekcC+8MCV2iaTd5JemhFkPD73QB+tK3iFC9P0xJC73B6MvKkyUfS9cCw==", "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/compat-data": "^7.12.13", "@babel/compat-data": "^7.13.15",
"@babel/helper-validator-option": "^7.12.11", "@babel/helper-validator-option": "^7.12.17",
"browserslist": "^4.14.5", "browserslist": "^4.14.5",
"semver": "^5.5.0" "semver": "^6.3.0"
},
"dependencies": {
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
}
} }
}, },
"@babel/helper-create-class-features-plugin": { "@babel/helper-create-class-features-plugin": {
"version": "7.12.13", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.1.tgz",
"integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", "integrity": "sha512-r8rsUahG4ywm0QpGcCrLaUSOuNAISR3IZCg4Fx05Ozq31aCUrQsTLH6KPxy0N5ULoQ4Sn9qjNdGNtbPWAC6hYg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-annotate-as-pure": "^7.12.13",
"@babel/helper-function-name": "^7.12.13", "@babel/helper-function-name": "^7.12.13",
"@babel/helper-member-expression-to-functions": "^7.12.13", "@babel/helper-member-expression-to-functions": "^7.13.12",
"@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13",
"@babel/helper-replace-supers": "^7.12.13", "@babel/helper-replace-supers": "^7.13.12",
"@babel/helper-split-export-declaration": "^7.12.13" "@babel/helper-split-export-declaration": "^7.12.13"
} }
}, },
"@babel/helper-create-regexp-features-plugin": { "@babel/helper-create-regexp-features-plugin": {
"version": "7.12.13", "version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz",
"integrity": "sha512-XC+kiA0J3at6E85dL5UnCYfVOcIZ834QcAY0TIpgUVnz0zDzg+0TtvZTnJ4g9L1dPRGe30Qi03XCIS4tYCLtqw==", "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-annotate-as-pure": "^7.12.13",
@@ -165,12 +174,12 @@
} }
}, },
"@babel/helper-explode-assignable-expression": { "@babel/helper-explode-assignable-expression": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz",
"integrity": "sha512-5loeRNvMo9mx1dA/d6yNi+YiKziJZFylZnCo1nmFF4qPU4yJ14abhWESuSMQSlQxWdxdOFzxXjk/PpfudTtYyw==", "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/types": "^7.13.0"
} }
}, },
"@babel/helper-function-name": { "@babel/helper-function-name": {
@@ -194,47 +203,47 @@
} }
}, },
"@babel/helper-hoist-variables": { "@babel/helper-hoist-variables": {
"version": "7.12.13", "version": "7.13.16",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz",
"integrity": "sha512-KSC5XSj5HreRhYQtZ3cnSnQwDzgnbdUDEFsxkN0m6Q3WrCRt72xrnZ8+h+pX7YxM7hr87zIO3a/v5p/H3TrnVw==", "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/traverse": "^7.13.15",
"@babel/types": "^7.13.16"
} }
}, },
"@babel/helper-member-expression-to-functions": { "@babel/helper-member-expression-to-functions": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
"integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/types": "^7.13.12"
} }
}, },
"@babel/helper-module-imports": { "@babel/helper-module-imports": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
"integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/types": "^7.13.12"
} }
}, },
"@babel/helper-module-transforms": { "@babel/helper-module-transforms": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz",
"integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", "integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-imports": "^7.12.13", "@babel/helper-module-imports": "^7.13.12",
"@babel/helper-replace-supers": "^7.12.13", "@babel/helper-replace-supers": "^7.13.12",
"@babel/helper-simple-access": "^7.12.13", "@babel/helper-simple-access": "^7.13.12",
"@babel/helper-split-export-declaration": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13",
"@babel/helper-validator-identifier": "^7.12.11", "@babel/helper-validator-identifier": "^7.14.0",
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13", "@babel/traverse": "^7.14.0",
"@babel/types": "^7.12.13", "@babel/types": "^7.14.0"
"lodash": "^4.17.19"
} }
}, },
"@babel/helper-optimise-call-expression": { "@babel/helper-optimise-call-expression": {
@@ -247,41 +256,41 @@
} }
}, },
"@babel/helper-plugin-utils": { "@babel/helper-plugin-utils": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz",
"integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==",
"dev": true "dev": true
}, },
"@babel/helper-remap-async-to-generator": { "@babel/helper-remap-async-to-generator": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz",
"integrity": "sha512-Qa6PU9vNcj1NZacZZI1Mvwt+gXDH6CTfgAkSjeRMLE8HxtDK76+YDId6NQR+z7Rgd5arhD2cIbS74r0SxD6PDA==", "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-annotate-as-pure": "^7.12.13",
"@babel/helper-wrap-function": "^7.12.13", "@babel/helper-wrap-function": "^7.13.0",
"@babel/types": "^7.12.13" "@babel/types": "^7.13.0"
} }
}, },
"@babel/helper-replace-supers": { "@babel/helper-replace-supers": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz",
"integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-member-expression-to-functions": "^7.12.13", "@babel/helper-member-expression-to-functions": "^7.13.12",
"@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13",
"@babel/traverse": "^7.12.13", "@babel/traverse": "^7.13.0",
"@babel/types": "^7.12.13" "@babel/types": "^7.13.12"
} }
}, },
"@babel/helper-simple-access": { "@babel/helper-simple-access": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
"integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.12.13" "@babel/types": "^7.13.12"
} }
}, },
"@babel/helper-skip-transparent-expression-wrappers": { "@babel/helper-skip-transparent-expression-wrappers": {
@@ -303,66 +312,66 @@
} }
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.12.11", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==",
"dev": true "dev": true
}, },
"@babel/helper-validator-option": { "@babel/helper-validator-option": {
"version": "7.12.11", "version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz",
"integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==",
"dev": true "dev": true
}, },
"@babel/helper-wrap-function": { "@babel/helper-wrap-function": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz",
"integrity": "sha512-t0aZFEmBJ1LojdtJnhOaQEVejnzYhyjWHSsNSNo8vOYRbAJNh6r6GQF7pd36SqG7OKGbn+AewVQ/0IfYfIuGdw==", "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-function-name": "^7.12.13", "@babel/helper-function-name": "^7.12.13",
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13", "@babel/traverse": "^7.13.0",
"@babel/types": "^7.12.13" "@babel/types": "^7.13.0"
} }
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz",
"integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13", "@babel/traverse": "^7.14.0",
"@babel/types": "^7.12.13" "@babel/types": "^7.14.0"
} }
}, },
"@babel/highlight": { "@babel/highlight": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz",
"integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.12.11", "@babel/helper-validator-identifier": "^7.14.0",
"chalk": "^2.0.0", "chalk": "^2.0.0",
"js-tokens": "^4.0.0" "js-tokens": "^4.0.0"
} }
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.12.15", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.1.tgz",
"integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", "integrity": "sha512-muUGEKu8E/ftMTPlNp+mc6zL3E9zKWmF5sDHZ5MSsoTP9Wyz64AhEf9kD08xYJ7w6Hdcu8H550ircnPyWSIF0Q==",
"dev": true "dev": true
}, },
"@babel/plugin-proposal-async-generator-functions": { "@babel/plugin-proposal-async-generator-functions": {
"version": "7.12.13", "version": "7.13.15",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz",
"integrity": "sha512-1KH46Hx4WqP77f978+5Ye/VUbuwQld2hph70yaw2hXS2v7ER2f3nlpNMu909HO2rbvP0NKLlMVDPh9KXklVMhA==", "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-remap-async-to-generator": "^7.12.13", "@babel/helper-remap-async-to-generator": "^7.13.0",
"@babel/plugin-syntax-async-generators": "^7.8.0" "@babel/plugin-syntax-async-generators": "^7.8.4"
} }
}, },
"@babel/plugin-proposal-class-properties": { "@babel/plugin-proposal-class-properties": {
@@ -376,33 +385,33 @@
} }
}, },
"@babel/plugin-proposal-dynamic-import": { "@babel/plugin-proposal-dynamic-import": {
"version": "7.12.1", "version": "7.13.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz",
"integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.0" "@babel/plugin-syntax-dynamic-import": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-json-strings": { "@babel/plugin-proposal-json-strings": {
"version": "7.12.13", "version": "7.13.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz",
"integrity": "sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg==", "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/plugin-syntax-json-strings": "^7.8.0" "@babel/plugin-syntax-json-strings": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-nullish-coalescing-operator": { "@babel/plugin-proposal-nullish-coalescing-operator": {
"version": "7.12.13", "version": "7.13.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz",
"integrity": "sha512-Qoxpy+OxhDBI5kRqliJFAl4uWXk3Bn24WeFstPH0iLymFehSAUR8MHpqU7njyXv/qbo7oN6yTy5bfCmXdKpo1Q==", "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-numeric-separator": { "@babel/plugin-proposal-numeric-separator": {
@@ -427,34 +436,34 @@
} }
}, },
"@babel/plugin-proposal-optional-catch-binding": { "@babel/plugin-proposal-optional-catch-binding": {
"version": "7.12.13", "version": "7.13.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz",
"integrity": "sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg==", "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.0" "@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-optional-chaining": { "@babel/plugin-proposal-optional-chaining": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz",
"integrity": "sha512-0ZwjGfTcnZqyV3y9DSD1Yk3ebp+sIUpT2YDqP8hovzaNZnQq2Kd7PEqa6iOIUDBXBt7Jl3P7YAcEIL5Pz8u09Q==", "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1",
"@babel/plugin-syntax-optional-chaining": "^7.8.0" "@babel/plugin-syntax-optional-chaining": "^7.8.3"
} }
}, },
"@babel/plugin-proposal-private-methods": { "@babel/plugin-proposal-private-methods": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz",
"integrity": "sha512-sV0V57uUwpauixvR7s2o75LmwJI6JECwm5oPUY5beZB1nBl2i37hc7CJGqB5G+58fur5Y6ugvl3LRONk5x34rg==", "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-create-class-features-plugin": "^7.12.13", "@babel/helper-create-class-features-plugin": "^7.13.0",
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-proposal-unicode-property-regex": { "@babel/plugin-proposal-unicode-property-regex": {
@@ -594,23 +603,23 @@
} }
}, },
"@babel/plugin-transform-arrow-functions": { "@babel/plugin-transform-arrow-functions": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz",
"integrity": "sha512-tBtuN6qtCTd+iHzVZVOMNp+L04iIJBpqkdY42tWbmjIT5wvR2kx7gxMBsyhQtFzHwBbyGi9h8J8r9HgnOpQHxg==", "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-async-to-generator": { "@babel/plugin-transform-async-to-generator": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz",
"integrity": "sha512-psM9QHcHaDr+HZpRuJcE1PXESuGWSCcbiGFFhhwfzdbTxaGDVzuVtdNYliAwcRo3GFg0Bc8MmI+AvIGYIJG04A==", "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-imports": "^7.12.13", "@babel/helper-module-imports": "^7.12.13",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-remap-async-to-generator": "^7.12.13" "@babel/helper-remap-async-to-generator": "^7.13.0"
} }
}, },
"@babel/plugin-transform-block-scoped-functions": { "@babel/plugin-transform-block-scoped-functions": {
@@ -623,45 +632,45 @@
} }
}, },
"@babel/plugin-transform-block-scoping": { "@babel/plugin-transform-block-scoping": {
"version": "7.12.13", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.1.tgz",
"integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", "integrity": "sha512-2mQXd0zBrwfp0O1moWIhPpEeTKDvxyHcnma3JATVP1l+CctWBuot6OJG8LQ4DnBj4ZZPSmlb/fm4mu47EOAnVA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-classes": { "@babel/plugin-transform-classes": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz",
"integrity": "sha512-cqZlMlhCC1rVnxE5ZGMtIb896ijL90xppMiuWXcwcOAuFczynpd3KYemb91XFFPi3wJSe/OcrX9lXoowatkkxA==", "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-annotate-as-pure": "^7.12.13",
"@babel/helper-function-name": "^7.12.13", "@babel/helper-function-name": "^7.12.13",
"@babel/helper-optimise-call-expression": "^7.12.13", "@babel/helper-optimise-call-expression": "^7.12.13",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-replace-supers": "^7.12.13", "@babel/helper-replace-supers": "^7.13.0",
"@babel/helper-split-export-declaration": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13",
"globals": "^11.1.0" "globals": "^11.1.0"
} }
}, },
"@babel/plugin-transform-computed-properties": { "@babel/plugin-transform-computed-properties": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz",
"integrity": "sha512-dDfuROUPGK1mTtLKyDPUavmj2b6kFu82SmgpztBFEO974KMjJT+Ytj3/oWsTUMBmgPcp9J5Pc1SlcAYRpJ2hRA==", "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-destructuring": { "@babel/plugin-transform-destructuring": {
"version": "7.12.13", "version": "7.13.17",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz",
"integrity": "sha512-Dn83KykIFzjhA3FDPA1z4N+yfF3btDGhjnJwxIj0T43tP0flCujnU8fKgEkf0C1biIpSv9NZegPBQ1J6jYkwvQ==", "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-dotall-regex": { "@babel/plugin-transform-dotall-regex": {
@@ -694,12 +703,12 @@
} }
}, },
"@babel/plugin-transform-for-of": { "@babel/plugin-transform-for-of": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz",
"integrity": "sha512-xCbdgSzXYmHGyVX3+BsQjcd4hv4vA/FDy7Kc8eOpzKmBBPEOTurt0w5fCRQaGl+GSBORKgJdstQ1rHl4jbNseQ==", "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-function-name": { "@babel/plugin-transform-function-name": {
@@ -731,49 +740,49 @@
} }
}, },
"@babel/plugin-transform-modules-amd": { "@babel/plugin-transform-modules-amd": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.0.tgz",
"integrity": "sha512-JHLOU0o81m5UqG0Ulz/fPC68/v+UTuGTWaZBUwpEk1fYQ1D9LfKV6MPn4ttJKqRo5Lm460fkzjLTL4EHvCprvA==", "integrity": "sha512-CF4c5LX4LQ03LebQxJ5JZes2OYjzBuk1TdiF7cG7d5dK4lAdw9NZmaxq5K/mouUdNeqwz3TNjnW6v01UqUNgpQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-transforms": "^7.12.13", "@babel/helper-module-transforms": "^7.14.0",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"babel-plugin-dynamic-import-node": "^2.3.3" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-commonjs": { "@babel/plugin-transform-modules-commonjs": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz",
"integrity": "sha512-OGQoeVXVi1259HjuoDnsQMlMkT9UkZT9TpXAsqWplS/M0N1g3TJAn/ByOCeQu7mfjc5WpSsRU+jV1Hd89ts0kQ==", "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-transforms": "^7.12.13", "@babel/helper-module-transforms": "^7.14.0",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-simple-access": "^7.12.13", "@babel/helper-simple-access": "^7.13.12",
"babel-plugin-dynamic-import-node": "^2.3.3" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-systemjs": { "@babel/plugin-transform-modules-systemjs": {
"version": "7.12.13", "version": "7.13.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz",
"integrity": "sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA==", "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-hoist-variables": "^7.12.13", "@babel/helper-hoist-variables": "^7.13.0",
"@babel/helper-module-transforms": "^7.12.13", "@babel/helper-module-transforms": "^7.13.0",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-validator-identifier": "^7.12.11", "@babel/helper-validator-identifier": "^7.12.11",
"babel-plugin-dynamic-import-node": "^2.3.3" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-umd": { "@babel/plugin-transform-modules-umd": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz",
"integrity": "sha512-BgZndyABRML4z6ibpi7Z98m4EVLFI9tVsZDADC14AElFaNHHBcJIovflJ6wtCqFxwy2YJ1tJhGRsr0yLPKoN+w==", "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-transforms": "^7.12.13", "@babel/helper-module-transforms": "^7.14.0",
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-named-capturing-groups-regex": { "@babel/plugin-transform-named-capturing-groups-regex": {
@@ -805,12 +814,12 @@
} }
}, },
"@babel/plugin-transform-parameters": { "@babel/plugin-transform-parameters": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz",
"integrity": "sha512-e7QqwZalNiBRHCpJg/P8s/VJeSRYgmtWySs1JwvfwPqhBbiWfOcHDKdeAi6oAyIimoKWBlwc8oTgbZHdhCoVZA==", "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-property-literals": { "@babel/plugin-transform-property-literals": {
@@ -823,12 +832,12 @@
} }
}, },
"@babel/plugin-transform-react-constant-elements": { "@babel/plugin-transform-react-constant-elements": {
"version": "7.12.13", "version": "7.13.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.13.13.tgz",
"integrity": "sha512-qmzKVTn46Upvtxv8LQoQ8mTCdUC83AOVQIQm57e9oekLT5cmK9GOMOfcWhe8jMNx4UJXn/UDhVZ/7lGofVNeDQ==", "integrity": "sha512-SNJU53VM/SjQL0bZhyU+f4kJQz7bQQajnrZRSaU21hruG/NWY41AEM9AWXeXX90pYr/C2yAmTgI6yW3LlLrAUQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-react-display-name": { "@babel/plugin-transform-react-display-name": {
@@ -841,25 +850,25 @@
} }
}, },
"@babel/plugin-transform-react-jsx": { "@babel/plugin-transform-react-jsx": {
"version": "7.12.13", "version": "7.13.12",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz",
"integrity": "sha512-hhXZMYR8t9RvduN2uW4sjl6MRtUhzNE726JvoJhpjhxKgRUVkZqTsA0xc49ALZxQM7H26pZ/lLvB2Yrea9dllA==", "integrity": "sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-annotate-as-pure": "^7.12.13",
"@babel/helper-module-imports": "^7.12.13", "@babel/helper-module-imports": "^7.13.12",
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/plugin-syntax-jsx": "^7.12.13", "@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/types": "^7.12.13" "@babel/types": "^7.13.12"
} }
}, },
"@babel/plugin-transform-react-jsx-development": { "@babel/plugin-transform-react-jsx-development": {
"version": "7.12.12", "version": "7.12.17",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.12.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.12.17.tgz",
"integrity": "sha512-i1AxnKxHeMxUaWVXQOSIco4tvVvvCxMSfeBMnMM06mpaJt3g+MpxYQQrDfojUQldP1xxraPSJYSMEljoWM/dCg==", "integrity": "sha512-BPjYV86SVuOaudFhsJR1zjgxxOhJDt6JHNoD48DxWEIxUCAMjV1ys6DYw4SDYZh0b1QsS2vfIA9t/ZsQGsDOUQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/plugin-transform-react-jsx": "^7.12.12" "@babel/plugin-transform-react-jsx": "^7.12.17"
} }
}, },
"@babel/plugin-transform-react-jsx-self": { "@babel/plugin-transform-react-jsx-self": {
@@ -891,9 +900,9 @@
} }
}, },
"@babel/plugin-transform-regenerator": { "@babel/plugin-transform-regenerator": {
"version": "7.12.13", "version": "7.13.15",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz",
"integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"regenerator-transform": "^0.14.2" "regenerator-transform": "^0.14.2"
@@ -918,12 +927,12 @@
} }
}, },
"@babel/plugin-transform-spread": { "@babel/plugin-transform-spread": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz",
"integrity": "sha512-dUCrqPIowjqk5pXsx1zPftSq4sT0aCeZVAxhdgs3AMgyaDmoUT0G+5h3Dzja27t76aUEIJWlFgPJqJ/d4dbTtg==", "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0",
"@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1"
} }
}, },
@@ -937,12 +946,12 @@
} }
}, },
"@babel/plugin-transform-template-literals": { "@babel/plugin-transform-template-literals": {
"version": "7.12.13", "version": "7.13.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz",
"integrity": "sha512-arIKlWYUgmNsF28EyfmiQHJLJFlAJNYkuQO10jL46ggjBpeb2re1P9K9YGxNJB45BqTbaslVysXDYm/g3sN/Qg==", "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.12.13" "@babel/helper-plugin-utils": "^7.13.0"
} }
}, },
"@babel/plugin-transform-typeof-symbol": { "@babel/plugin-transform-typeof-symbol": {
@@ -1074,17 +1083,17 @@
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
"integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==", "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.13.4"
} }
}, },
"@babel/runtime-corejs3": { "@babel/runtime-corejs3": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz",
"integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==", "integrity": "sha512-0R0HTZWHLk6G8jIk0FtoX+AatCtKnswS98VhXwGImFc759PJRp4Tru0PQYZofyijTFUr+gT8Mu7sgXVJLQ0ceg==",
"dev": true, "dev": true,
"requires": { "requires": {
"core-js-pure": "^3.0.0", "core-js-pure": "^3.0.0",
@@ -1103,20 +1112,19 @@
} }
}, },
"@babel/traverse": { "@babel/traverse": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz",
"integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.12.13", "@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.12.13", "@babel/generator": "^7.14.0",
"@babel/helper-function-name": "^7.12.13", "@babel/helper-function-name": "^7.12.13",
"@babel/helper-split-export-declaration": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13",
"@babel/parser": "^7.12.13", "@babel/parser": "^7.14.0",
"@babel/types": "^7.12.13", "@babel/types": "^7.14.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0", "globals": "^11.1.0"
"lodash": "^4.17.19"
}, },
"dependencies": { "dependencies": {
"debug": { "debug": {
@@ -1137,13 +1145,12 @@
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.12.13", "version": "7.14.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.1.tgz",
"integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", "integrity": "sha512-S13Qe85fzLs3gYRUnrpyeIrBJIMYv33qSTg1qoBwiG6nPKwUWAD9odSzWhEedpwOIzSEI6gbdQIWEMiCI42iBA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.12.11", "@babel/helper-validator-identifier": "^7.14.0",
"lodash": "^4.17.19",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
@@ -1164,9 +1171,9 @@
} }
}, },
"@cospired/i18n-iso-languages": { "@cospired/i18n-iso-languages": {
"version": "2.1.2", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-2.1.2.tgz", "resolved": "https://registry.npmjs.org/@cospired/i18n-iso-languages/-/i18n-iso-languages-2.2.0.tgz",
"integrity": "sha512-XylKOsWRyQm9sNanZnppRORXTLaL34uThyBQpTFwOGAYvNg9PeYsyTTfLA1FTCh02RV+kiwt/O/y14DR/OqpWg==" "integrity": "sha512-hywY9u9apWGeLxQuRcXw7IW0XkMdXum/hr3TpmHY2fAbXMTFlhhkPCdsQeHzjxMQwTnMgXaZ4j4WOCwKtlDRCQ=="
}, },
"@edx/brand": { "@edx/brand": {
"version": "npm:@edx/brand-openedx@1.1.0", "version": "npm:@edx/brand-openedx@1.1.0",
@@ -1246,9 +1253,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -1333,19 +1340,6 @@
"@fortawesome/react-fontawesome": "0.1.14" "@fortawesome/react-fontawesome": "0.1.14"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": {
"version": "0.2.34",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz",
"integrity": "sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA=="
},
"@fortawesome/fontawesome-svg-core": {
"version": "1.2.34",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.34.tgz",
"integrity": "sha512-0KNN0nc5eIzaJxlv43QcDmTkDY1CqeN6J7OCGSs+fwGPdtv0yOQqRjieopBCmw+yd7uD3N2HeNL3Zm5isDleLg==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.34"
}
},
"@fortawesome/free-brands-svg-icons": { "@fortawesome/free-brands-svg-icons": {
"version": "5.8.2", "version": "5.8.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.8.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.8.2.tgz",
@@ -1369,14 +1363,6 @@
"requires": { "requires": {
"@fortawesome/fontawesome-common-types": "^0.2.18" "@fortawesome/fontawesome-common-types": "^0.2.18"
} }
},
"@fortawesome/react-fontawesome": {
"version": "0.1.14",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz",
"integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==",
"requires": {
"prop-types": "^15.7.2"
}
} }
} }
}, },
@@ -1388,16 +1374,56 @@
"query-string": "^6.13.2" "query-string": "^6.13.2"
} }
}, },
"@edx/frontend-platform": { "@edx/frontend-lib-special-exams": {
"version": "1.8.4", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.8.4.tgz", "resolved": "https://registry.npmjs.org/@edx/frontend-lib-special-exams/-/frontend-lib-special-exams-1.0.0.tgz",
"integrity": "sha512-Xjl5VYFK+OrOY8TaMMGfDK45+dkxr0ai/6SsNDt3KCLTqIqsNLY19mPGsHHpZD8B3H5unQuWTn6VoRZ7q+Ovxg==", "integrity": "sha512-tVF38zD1+madCvxFb0HT3WEfGIPyQaaMr64y7bA/JMkPR34o8Zz6PibhNymmQESElNWZtavLdYxX5wCRIIhTxA==",
"requires": { "requires": {
"@cospired/i18n-iso-languages": "2.1.2", "@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.11.2",
"@fortawesome/free-regular-svg-icons": "5.11.2",
"@fortawesome/free-solid-svg-icons": "5.11.2",
"@fortawesome/react-fontawesome": "0.1.14",
"babel-polyfill": "6.26.0",
"eventemitter3": "^4.0.7"
},
"dependencies": {
"@fortawesome/free-brands-svg-icons": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.11.2.tgz",
"integrity": "sha512-wKK5znpHiZ2S0VgOvbeAnYuzkk3H86rxWajD9PVpfBj3s/kySEWTFKh/uLPyxiTOx8Tsd0OGN4En/s9XudVHLQ==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.25"
}
},
"@fortawesome/free-regular-svg-icons": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.11.2.tgz",
"integrity": "sha512-k0vbThRv9AvnXYBWi1gn1rFW4X7co/aFkbm0ZNmAR5PoWb9vY9EDDDobg8Ay4ISaXtCPypvJ0W1FWkSpLQwZ6w==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.25"
}
},
"@fortawesome/free-solid-svg-icons": {
"version": "5.11.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.11.2.tgz",
"integrity": "sha512-zBue4i0PAZJUXOmLBBvM7L0O7wmsDC8dFv9IhpW5QL4kT9xhhVUsYg/LX1+5KaukWq4/cbDcKT+RT1aRe543sg==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.25"
}
}
}
},
"@edx/frontend-platform": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-1.11.0.tgz",
"integrity": "sha512-XtqKPWUvXzPJLlIEsoLMuac3TvTtAe1GY9MKu1QQsZDget1plDZqaf3ByRbuxOW8b2g2JVPvFx+Jm3FxTcrmIQ==",
"requires": {
"@cospired/i18n-iso-languages": "2.2.0",
"axios": "0.21.1", "axios": "0.21.1",
"axios-cache-adapter": "^2.5.0", "axios-cache-adapter": "^2.5.0",
"form-urlencoded": "4.1.4", "form-urlencoded": "4.1.4",
"glob": "7.1.6", "glob": "7.1.7",
"history": "4.10.1", "history": "4.10.1",
"i18n-iso-countries": "4.3.1", "i18n-iso-countries": "4.3.1",
"jwt-decode": "2.2.0", "jwt-decode": "2.2.0",
@@ -1407,7 +1433,7 @@
"lodash.memoize": "4.1.2", "lodash.memoize": "4.1.2",
"lodash.merge": "4.6.2", "lodash.merge": "4.6.2",
"lodash.snakecase": "4.1.1", "lodash.snakecase": "4.1.1",
"pubsub-js": "1.7.0", "pubsub-js": "1.9.3",
"react-intl": "2.9.0", "react-intl": "2.9.0",
"universal-cookie": "4.0.4" "universal-cookie": "4.0.4"
} }
@@ -1440,11 +1466,6 @@
"uncontrollable": "7.2.1" "uncontrollable": "7.2.1"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-common-types": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
"integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw=="
},
"@fortawesome/free-solid-svg-icons": { "@fortawesome/free-solid-svg-icons": {
"version": "5.15.3", "version": "5.15.3",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz",
@@ -1465,9 +1486,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -1492,9 +1513,9 @@
} }
}, },
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -1521,17 +1542,17 @@
} }
}, },
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
}, },
"@fortawesome/fontawesome-common-types": { "@fortawesome/fontawesome-common-types": {
"version": "0.2.34", "version": "0.2.35",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.34.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz",
"integrity": "sha512-XcIn3iYbTEzGIxD0/dY5+4f019jIcEIWBiHc3KrmK/ROahwxmZ/s+tdj97p/5K0klz4zZUiMfUlYP0ajhSJjmA==" "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw=="
}, },
"@fortawesome/fontawesome-svg-core": { "@fortawesome/fontawesome-svg-core": {
"version": "1.2.34", "version": "1.2.34",
@@ -1601,9 +1622,9 @@
} }
}, },
"@istanbuljs/schema": { "@istanbuljs/schema": {
"version": "0.1.2", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"dev": true "dev": true
}, },
"@jest/console": { "@jest/console": {
@@ -1630,9 +1651,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -1732,9 +1753,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -1778,13 +1799,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"rimraf": { "rimraf": {
@@ -1911,9 +1932,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -2036,9 +2057,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -2082,13 +2103,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"slash": { "slash": {
@@ -2140,9 +2161,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -2257,9 +2278,9 @@
} }
}, },
"y18n": { "y18n": {
"version": "5.0.5", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true "dev": true
}, },
"yargs": { "yargs": {
@@ -2278,9 +2299,9 @@
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "20.2.4", "version": "20.2.7",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz",
"integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==",
"dev": true "dev": true
} }
} }
@@ -2349,9 +2370,9 @@
"optional": true "optional": true
}, },
"@sinonjs/commons": { "@sinonjs/commons": {
"version": "1.8.2", "version": "1.8.3",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz",
"integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"type-detect": "4.0.8" "type-detect": "4.0.8"
@@ -2463,25 +2484,25 @@
}, },
"dependencies": { "dependencies": {
"@babel/core": { "@babel/core": {
"version": "7.12.13", "version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.13.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz",
"integrity": "sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw==", "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.12.13", "@babel/code-frame": "^7.12.13",
"@babel/generator": "^7.12.13", "@babel/generator": "^7.14.0",
"@babel/helper-module-transforms": "^7.12.13", "@babel/helper-compilation-targets": "^7.13.16",
"@babel/helpers": "^7.12.13", "@babel/helper-module-transforms": "^7.14.0",
"@babel/parser": "^7.12.13", "@babel/helpers": "^7.14.0",
"@babel/parser": "^7.14.0",
"@babel/template": "^7.12.13", "@babel/template": "^7.12.13",
"@babel/traverse": "^7.12.13", "@babel/traverse": "^7.14.0",
"@babel/types": "^7.12.13", "@babel/types": "^7.14.0",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.1", "gensync": "^1.0.0-beta.2",
"json5": "^2.1.2", "json5": "^2.1.2",
"lodash": "^4.17.19", "semver": "^6.3.0",
"semver": "^5.4.1",
"source-map": "^0.5.0" "source-map": "^0.5.0"
} }
}, },
@@ -2500,6 +2521,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true "dev": true
}, },
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"source-map": { "source-map": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -2837,9 +2864,9 @@
"dev": true "dev": true
}, },
"@types/babel__core": { "@types/babel__core": {
"version": "7.1.12", "version": "7.1.14",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz",
"integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/parser": "^7.1.0", "@babel/parser": "^7.1.0",
@@ -2869,18 +2896,18 @@
} }
}, },
"@types/babel__traverse": { "@types/babel__traverse": {
"version": "7.11.0", "version": "7.11.1",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz",
"integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.3.0" "@babel/types": "^7.3.0"
} }
}, },
"@types/cheerio": { "@types/cheerio": {
"version": "0.22.23", "version": "0.22.28",
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.23.tgz", "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.28.tgz",
"integrity": "sha512-QfHLujVMlGqcS/ePSf3Oe5hK3H8wi/yN2JYuxSB1U10VvW1fO3K8C+mURQesFYS1Hn7lspOsTT75SKq/XtydQg==", "integrity": "sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw==",
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
@@ -2899,9 +2926,9 @@
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
}, },
"@types/fs-extra": { "@types/fs-extra": {
"version": "9.0.6", "version": "9.0.11",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.11.tgz",
"integrity": "sha512-ecNRHw4clCkowNOBJH1e77nvbPxHYnWIXMv1IAoG/9+MYGkgoyr3Ppxr7XYFNL41V422EDhyV4/4SSK8L2mlig==", "integrity": "sha512-mZsifGG4QeQ7hlkhO56u7zt/ycBgGxSVsFI/6lGTU34VtwkiqrrSDgw0+ygs8kFGWcXnFQWMrzF2h7TtDFNixA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
@@ -2918,14 +2945,23 @@
} }
}, },
"@types/graceful-fs": { "@types/graceful-fs": {
"version": "4.1.4", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
"integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/hoist-non-react-statics": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
"requires": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"@types/html-minifier-terser": { "@types/html-minifier-terser": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz",
@@ -2962,9 +2998,9 @@
} }
}, },
"@types/jest": { "@types/jest": {
"version": "26.0.20", "version": "26.0.23",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz",
"integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==",
"dev": true, "dev": true,
"requires": { "requires": {
"jest-diff": "^26.0.0", "jest-diff": "^26.0.0",
@@ -2984,15 +3020,15 @@
"dev": true "dev": true
}, },
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.3", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==",
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "14.14.25", "version": "15.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz",
"integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==" "integrity": "sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA=="
}, },
"@types/normalize-package-data": { "@types/normalize-package-data": {
"version": "2.4.0", "version": "2.4.0",
@@ -3007,9 +3043,9 @@
"dev": true "dev": true
}, },
"@types/prettier": { "@types/prettier": {
"version": "2.2.0", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz",
"integrity": "sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg==", "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==",
"dev": true "dev": true
}, },
"@types/prop-types": { "@types/prop-types": {
@@ -3024,15 +3060,26 @@
"dev": true "dev": true
}, },
"@types/react": { "@types/react": {
"version": "17.0.4", "version": "17.0.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.5.tgz",
"integrity": "sha512-onz2BqScSFMoTRdJUZUDD/7xrusM8hBA2Fktk2qgaTYPCgPvWnDEgkrOs8hhPUf2jfcIXkJ5yK6VfYormJS3Jw==", "integrity": "sha512-bj4biDB9ZJmGAYTWSKJly6bMr4BLUiBrx9ujiJEoP9XIDY9CTaPGxE5QWN/1WjpPLzYF7/jRNnV2nNxNe970sw==",
"requires": { "requires": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
}, },
"@types/react-redux": {
"version": "7.1.16",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz",
"integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-transition-group": { "@types/react-transition-group": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz",
@@ -3068,9 +3115,9 @@
"dev": true "dev": true
}, },
"@types/tapable": { "@types/tapable": {
"version": "1.0.6", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz",
"integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==", "integrity": "sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==",
"dev": true "dev": true
}, },
"@types/testing-library__jest-dom": { "@types/testing-library__jest-dom": {
@@ -3083,9 +3130,9 @@
} }
}, },
"@types/uglify-js": { "@types/uglify-js": {
"version": "3.12.0", "version": "3.13.0",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.12.0.tgz", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.0.tgz",
"integrity": "sha512-sYAF+CF9XZ5cvEBkI7RtrG9g2GtMBkviTnBxYYyq+8BWvO4QtXfwwR6a2LFwCi4evMKZfpv6U43ViYvv17Wz3Q==", "integrity": "sha512-EGkrJD5Uy+Pg0NUR8uA4bJ5WMfljyad0G+784vLCNUkD+QwOJXUbBYExXfVGf7YtyzdQp3L/XMYcliB987kL5Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"source-map": "^0.6.1" "source-map": "^0.6.1"
@@ -3097,14 +3144,14 @@
"integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI="
}, },
"@types/webpack": { "@types/webpack": {
"version": "4.41.26", "version": "4.41.28",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.28.tgz",
"integrity": "sha512-7ZyTfxjCRwexh+EJFwRUM+CDB2XvgHl4vfuqf1ZKrgGvcS5BrNvPQqJh3tsZ0P6h6Aa1qClVHaJZszLPzpqHeA==", "integrity": "sha512-Nn84RAiJjKRfPFFCVR8LC4ueTtTdfWAMZ03THIzZWRJB+rX24BD3LqPSFnbMscWauEsT4segAsylPDIaZyZyLQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/anymatch": "*", "@types/anymatch": "*",
"@types/node": "*", "@types/node": "*",
"@types/tapable": "*", "@types/tapable": "^1",
"@types/uglify-js": "*", "@types/uglify-js": "*",
"@types/webpack-sources": "*", "@types/webpack-sources": "*",
"source-map": "^0.6.0" "source-map": "^0.6.0"
@@ -3513,18 +3560,18 @@
"dev": true "dev": true
}, },
"ansi-escapes": { "ansi-escapes": {
"version": "4.3.1", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"type-fest": "^0.11.0" "type-fest": "^0.21.3"
}, },
"dependencies": { "dependencies": {
"type-fest": { "type-fest": {
"version": "0.11.0", "version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true "dev": true
} }
} }
@@ -3666,9 +3713,9 @@
"dev": true "dev": true
}, },
"aria-hidden": { "aria-hidden": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.2.tgz", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.1.3.tgz",
"integrity": "sha512-WAMH9q3vRimVqP+B0q2eDvx7IPDoY17A2fWwj5atTA/zTYJCNcS6HJ5YErZ5FO3PUHhrV0y0yR1NA0dRNm913A==", "integrity": "sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==",
"requires": { "requires": {
"tslib": "^1.0.0" "tslib": "^1.0.0"
} }
@@ -3735,40 +3782,16 @@
"dev": true "dev": true
}, },
"array-includes": { "array-includes": {
"version": "3.1.2", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz",
"integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1", "es-abstract": "^1.18.0-next.2",
"get-intrinsic": "^1.0.1", "get-intrinsic": "^1.1.1",
"is-string": "^1.0.5" "is-string": "^1.0.5"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"array-union": { "array-union": {
@@ -3810,30 +3833,6 @@
"call-bind": "^1.0.0", "call-bind": "^1.0.0",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1" "es-abstract": "^1.18.0-next.1"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"arrify": { "arrify": {
@@ -3864,9 +3863,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -3984,9 +3983,9 @@
} }
}, },
"axios-cache-adapter": { "axios-cache-adapter": {
"version": "2.7.0", "version": "2.7.3",
"resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.0.tgz", "resolved": "https://registry.npmjs.org/axios-cache-adapter/-/axios-cache-adapter-2.7.3.tgz",
"integrity": "sha512-itggIo9i8tnsBFnniNh8+7RxXfdCKZT+cEvyjzBdU8IIyudpj4WyrY7288KE8MICs6+u7YFRVlLZi3vvXufz8w==", "integrity": "sha512-A+ZKJ9lhpjthOEp4Z3QR/a9xC4du1ALaAsejgRGrH9ef6kSDxdFrhRpulqsh9khsEnwXxGfgpUuDp1YXMNMEiQ==",
"requires": { "requires": {
"cache-control-esm": "1.0.0", "cache-control-esm": "1.0.0",
"md5": "^2.2.1" "md5": "^2.2.1"
@@ -4115,9 +4114,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -4250,12 +4249,12 @@
}, },
"dependencies": { "dependencies": {
"intl-messageformat-parser": { "intl-messageformat-parser": {
"version": "5.4.2", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-5.4.2.tgz", "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz",
"integrity": "sha512-VHu6UWgWLJykaSeI1M2DpZMVRLuGCOV91i5I81xnJuAI0MKHP7ZJ3my5naOQkzG10ris3hBr+o5RElF1wQ5IXA==", "integrity": "sha512-TvB3LqF2VtP6yI6HXlRT5TxX98HKha6hCcrg9dwlPwNaedVNuQA9KgBdtWKgiyakyCTYHQ+KJeFEstNKfZr64w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@formatjs/intl-numberformat": "^5.5.0" "@formatjs/intl-numberformat": "^5.5.2"
} }
} }
} }
@@ -4274,7 +4273,6 @@
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"dev": true,
"requires": { "requires": {
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"core-js": "^2.5.0", "core-js": "^2.5.0",
@@ -4284,14 +4282,12 @@
"core-js": { "core-js": {
"version": "2.6.12", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
"dev": true
}, },
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.10.5", "version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=", "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
"dev": true
} }
} }
}, },
@@ -4347,9 +4343,9 @@
} }
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"base": { "base": {
"version": "0.11.2", "version": "0.11.2",
@@ -4813,9 +4809,9 @@
"dev": true "dev": true
}, },
"bn.js": { "bn.js": {
"version": "5.1.3", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
"integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==",
"dev": true "dev": true
}, },
"body-parser": { "body-parser": {
@@ -5022,16 +5018,16 @@
} }
}, },
"browserslist": { "browserslist": {
"version": "4.16.3", "version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"caniuse-lite": "^1.0.30001181", "caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.1", "colorette": "^1.2.2",
"electron-to-chromium": "^1.3.649", "electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1", "escalade": "^3.1.1",
"node-releases": "^1.1.70" "node-releases": "^1.1.71"
} }
}, },
"bser": { "bser": {
@@ -5316,9 +5312,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -5362,9 +5358,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001185", "version": "1.0.30001228",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz",
"integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==", "integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==",
"dev": true "dev": true
}, },
"caporal": { "caporal": {
@@ -5564,13 +5560,10 @@
"dev": true "dev": true
}, },
"chrome-trace-event": { "chrome-trace-event": {
"version": "1.0.2", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
"integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
"dev": true, "dev": true
"requires": {
"tslib": "^1.9.0"
}
}, },
"ci-info": { "ci-info": {
"version": "2.0.0", "version": "2.0.0",
@@ -5774,28 +5767,16 @@
"dev": true "dev": true
}, },
"codecov": { "codecov": {
"version": "3.8.1", "version": "3.8.2",
"resolved": "https://registry.npmjs.org/codecov/-/codecov-3.8.1.tgz", "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.8.2.tgz",
"integrity": "sha512-Qm7ltx1pzLPsliZY81jyaQ80dcNR4/JpcX0IHCIWrHBXgseySqbdbYfkdiXd7o/xmzQpGRVCKGYeTrHUpn6Dcw==", "integrity": "sha512-6w/kt/xvmPsWMfDFPE/T054txA9RTgcJEw36PNa6MYX+YV29jCHCRFXwbQ3QZBTOgnex1J2WP8bo2AT8TWWz9g==",
"dev": true, "dev": true,
"requires": { "requires": {
"argv": "0.0.2", "argv": "0.0.2",
"ignore-walk": "3.0.3", "ignore-walk": "3.0.3",
"js-yaml": "3.14.0", "js-yaml": "3.14.1",
"teeny-request": "6.0.1", "teeny-request": "7.0.1",
"urlgrey": "0.4.4" "urlgrey": "0.4.4"
},
"dependencies": {
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
}
} }
}, },
"collect-v8-coverage": { "collect-v8-coverage": {
@@ -5838,9 +5819,9 @@
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
}, },
"color-string": { "color-string": {
"version": "1.5.4", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.4.tgz", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.5.tgz",
"integrity": "sha512-57yF5yt8Xa3czSEW1jfQDE79Idk0+AkN/4KWad6tbdxUmAs3MvjxlWSWD4deYytcRfoZ9nhKyFl1kj5tBvidbw==", "integrity": "sha512-jgIoum0OfQfq9Whcfc2z/VhCNcmQjWbey6qBX0vqt7YICflUmBCh9E9CiQD5GSJ+Uehixm3NUwHVhqUAWRivZg==",
"dev": true, "dev": true,
"requires": { "requires": {
"color-name": "^1.0.0", "color-name": "^1.0.0",
@@ -5848,9 +5829,9 @@
} }
}, },
"colorette": { "colorette": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true "dev": true
}, },
"colors": { "colors": {
@@ -6119,12 +6100,12 @@
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA=="
}, },
"core-js-compat": { "core-js-compat": {
"version": "3.8.3", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.12.1.tgz",
"integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "integrity": "sha512-i6h5qODpw6EsHAoIdQhKoZdWn+dGBF3dSS8m5tif36RlWvW3A6+yu2S16QHUo3CrkzrnEskMAt9f8FxmY9fhWQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "^4.16.1", "browserslist": "^4.16.6",
"semver": "7.0.0" "semver": "7.0.0"
}, },
"dependencies": { "dependencies": {
@@ -6137,9 +6118,9 @@
} }
}, },
"core-js-pure": { "core-js-pure": {
"version": "3.8.3", "version": "3.12.1",
"resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.12.1.tgz",
"integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==", "integrity": "sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ==",
"dev": true "dev": true
}, },
"core-util-is": { "core-util-is": {
@@ -6172,9 +6153,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -6352,9 +6333,9 @@
}, },
"dependencies": { "dependencies": {
"domelementtype": { "domelementtype": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A=="
} }
} }
}, },
@@ -6460,9 +6441,9 @@
} }
}, },
"cssnano-preset-default": { "cssnano-preset-default": {
"version": "4.0.7", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz",
"integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"css-declaration-sorter": "^4.0.1", "css-declaration-sorter": "^4.0.1",
@@ -6493,7 +6474,7 @@
"postcss-ordered-values": "^4.1.2", "postcss-ordered-values": "^4.1.2",
"postcss-reduce-initial": "^4.0.3", "postcss-reduce-initial": "^4.0.3",
"postcss-reduce-transforms": "^4.0.2", "postcss-reduce-transforms": "^4.0.2",
"postcss-svgo": "^4.0.2", "postcss-svgo": "^4.0.3",
"postcss-unique-selectors": "^4.0.1" "postcss-unique-selectors": "^4.0.1"
} }
}, },
@@ -6534,9 +6515,9 @@
}, },
"dependencies": { "dependencies": {
"css-tree": { "css-tree": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"mdn-data": "2.0.14", "mdn-data": "2.0.14",
@@ -6624,9 +6605,9 @@
} }
}, },
"damerau-levenshtein": { "damerau-levenshtein": {
"version": "1.0.6", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.7.tgz",
"integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", "integrity": "sha512-VvdQIPGdWP0SqFXghj79Wf/5LArmreyMsGLa6FG6iC4t3j7j5s71TrwWmT/4akbDQIqjfACkLZmjXhA7g2oUZw==",
"dev": true "dev": true
}, },
"dashdash": { "dashdash": {
@@ -6981,9 +6962,9 @@
"dev": true "dev": true
}, },
"detect-node": { "detect-node": {
"version": "2.0.4", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.5.tgz",
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "integrity": "sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw==",
"dev": true "dev": true
}, },
"detect-node-es": { "detect-node-es": {
@@ -7024,9 +7005,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -7185,9 +7166,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -7364,9 +7345,9 @@
"dev": true "dev": true
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.3.657", "version": "1.3.727",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.657.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz",
"integrity": "sha512-/9ROOyvEflEbaZFUeGofD+Tqs/WynbSTbNgNF+/TJJxH1ePD/e6VjZlDJpW3FFFd3nj5l3Hd8ki2vRwy+gyRFw==", "integrity": "sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg==",
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
@@ -7385,9 +7366,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -7521,21 +7502,26 @@
} }
}, },
"es-abstract": { "es-abstract": {
"version": "1.17.7", "version": "1.18.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
"integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", "integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
"requires": { "requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1", "es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1", "function-bind": "^1.1.1",
"get-intrinsic": "^1.1.1",
"has": "^1.0.3", "has": "^1.0.3",
"has-symbols": "^1.0.1", "has-symbols": "^1.0.2",
"is-callable": "^1.2.2", "is-callable": "^1.2.3",
"is-regex": "^1.1.1", "is-negative-zero": "^2.0.1",
"object-inspect": "^1.8.0", "is-regex": "^1.1.2",
"is-string": "^1.0.5",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1", "object-keys": "^1.1.1",
"object.assign": "^4.1.1", "object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.1", "string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.1" "string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.0"
} }
}, },
"es-check": { "es-check": {
@@ -7617,16 +7603,24 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"escodegen": { "escodegen": {
"version": "1.14.3", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
"dev": true, "dev": true,
"requires": { "requires": {
"esprima": "^4.0.1", "esprima": "^4.0.1",
"estraverse": "^4.2.0", "estraverse": "^5.2.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"optionator": "^0.8.1", "optionator": "^0.8.1",
"source-map": "~0.6.1" "source-map": "~0.6.1"
},
"dependencies": {
"estraverse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
"integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
"dev": true
}
} }
}, },
"eslint": { "eslint": {
@@ -7684,9 +7678,9 @@
} }
}, },
"glob-parent": { "glob-parent": {
"version": "5.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
@@ -8021,19 +8015,18 @@
"eventemitter3": { "eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
"dev": true
}, },
"events": { "events": {
"version": "3.2.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true "dev": true
}, },
"eventsource": { "eventsource": {
"version": "1.0.7", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.0.tgz",
"integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", "integrity": "sha512-VSJjT5oCNrFvCS6igjzPAt5hBzQ2qPBFIbJ03zLI9SE0mxwZpMw6BfJrbFHm1a141AavMEB8JHmBhWAd66PfCg==",
"dev": true, "dev": true,
"requires": { "requires": {
"original": "^1.0.0" "original": "^1.0.0"
@@ -8108,9 +8101,9 @@
} }
}, },
"exec-sh": { "exec-sh": {
"version": "0.3.4", "version": "0.3.6",
"resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz",
"integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==",
"dev": true "dev": true
}, },
"execa": { "execa": {
@@ -8317,9 +8310,9 @@
}, },
"dependencies": { "dependencies": {
"type": { "type": {
"version": "2.1.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", "resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
"integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", "integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==",
"dev": true "dev": true
} }
} }
@@ -8499,9 +8492,9 @@
} }
}, },
"glob-parent": { "glob-parent": {
"version": "5.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
@@ -8514,13 +8507,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"to-regex-range": { "to-regex-range": {
@@ -8546,10 +8539,17 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true "dev": true
}, },
"fast-xml-parser": {
"version": "3.19.0",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz",
"integrity": "sha512-4pXwmBplsCPv8FOY1WRakF970TjNGnGnfbOnLqjlYvMiF1SR3yOHyxMR/YCXpPTOspNF5gwudqktIP4VsWkvBg==",
"dev": true,
"optional": true
},
"fastq": { "fastq": {
"version": "1.10.1", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
"integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
"dev": true, "dev": true,
"requires": { "requires": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@@ -8691,6 +8691,11 @@
} }
} }
}, },
"filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs="
},
"finalhandler": { "finalhandler": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -8852,9 +8857,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.13.2", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==" "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
}, },
"font-awesome": { "font-awesome": {
"version": "4.7.0", "version": "4.7.0",
@@ -9085,54 +9090,6 @@
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.2", "es-abstract": "^1.18.0-next.2",
"functions-have-names": "^1.2.2" "functions-have-names": "^1.2.2"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
"integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.2",
"is-callable": "^1.2.3",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.2",
"is-string": "^1.0.5",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.4",
"string.prototype.trimstart": "^1.0.4",
"unbox-primitive": "^1.0.0"
}
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
},
"string.prototype.trimend": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
},
"string.prototype.trimstart": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
"requires": {
"call-bind": "^1.0.2",
"define-properties": "^1.1.3"
}
}
} }
}, },
"functional-red-black-tree": { "functional-red-black-tree": {
@@ -9247,9 +9204,9 @@
} }
}, },
"glob": { "glob": {
"version": "7.1.6", "version": "7.1.7",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
"inflight": "^1.0.4", "inflight": "^1.0.4",
@@ -9366,9 +9323,9 @@
} }
}, },
"graceful-fs": { "graceful-fs": {
"version": "4.2.5", "version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
"integrity": "sha512-kBBSQbz2K0Nyn+31j/w36fUfxkBW9/gfwRWdUY1ULReH3iokVJgddZAFcD1D0xlgTmFxJCbUkUclAlc6/IDJkw==", "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
"dev": true "dev": true
}, },
"growly": { "growly": {
@@ -9410,9 +9367,9 @@
} }
}, },
"harmony-reflect": { "harmony-reflect": {
"version": "1.6.1", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz", "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz",
"integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==", "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==",
"dev": true "dev": true
}, },
"has": { "has": {
@@ -9458,9 +9415,9 @@
"optional": true "optional": true
}, },
"has-symbols": { "has-symbols": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
}, },
"has-to-string-tag-x": { "has-to-string-tag-x": {
"version": "1.4.1", "version": "1.4.1",
@@ -9591,9 +9548,9 @@
"dev": true "dev": true
}, },
"hosted-git-info": { "hosted-git-info": {
"version": "2.8.8", "version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true "dev": true
}, },
"hpack.js": { "hpack.js": {
@@ -9658,12 +9615,6 @@
"integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=",
"dev": true "dev": true
}, },
"html-comment-regex": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz",
"integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==",
"dev": true
},
"html-encoding-sniffer": { "html-encoding-sniffer": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
@@ -9875,21 +9826,15 @@
"dev": true "dev": true
}, },
"https-proxy-agent": { "https-proxy-agent": {
"version": "4.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"dev": true, "dev": true,
"requires": { "requires": {
"agent-base": "5", "agent-base": "6",
"debug": "4" "debug": "4"
}, },
"dependencies": { "dependencies": {
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==",
"dev": true
},
"debug": { "debug": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
@@ -10265,18 +10210,6 @@
"requires": { "requires": {
"is-svg": "^4.2.1", "is-svg": "^4.2.1",
"svgo": "^1.3.2" "svgo": "^1.3.2"
},
"dependencies": {
"is-svg": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.2.1.tgz",
"integrity": "sha512-PHx3ANecKsKNl5y5+Jvt53Y4J7MfMpbNZkv384QNiswMKAWIbvcqbPz+sYbFKJI8Xv3be01GSFniPmoaP+Ai5A==",
"dev": true,
"optional": true,
"requires": {
"html-comment-regex": "^1.1.2"
}
}
} }
}, },
"imagemin-webp": { "imagemin-webp": {
@@ -10444,9 +10377,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -10626,9 +10559,9 @@
"dev": true "dev": true
}, },
"is-bigint": { "is-bigint": {
"version": "1.0.1", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz",
"integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==" "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA=="
}, },
"is-binary-path": { "is-binary-path": {
"version": "1.0.1", "version": "1.0.1",
@@ -10640,11 +10573,11 @@
} }
}, },
"is-boolean-object": { "is-boolean-object": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz",
"integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==",
"requires": { "requires": {
"call-bind": "^1.0.0" "call-bind": "^1.0.2"
} }
}, },
"is-buffer": { "is-buffer": {
@@ -10681,9 +10614,9 @@
} }
}, },
"is-core-module": { "is-core-module": {
"version": "2.2.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true, "dev": true,
"requires": { "requires": {
"has": "^1.0.3" "has": "^1.0.3"
@@ -10729,9 +10662,9 @@
} }
}, },
"is-date-object": { "is-date-object": {
"version": "1.0.2", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz",
"integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A=="
}, },
"is-descriptor": { "is-descriptor": {
"version": "0.1.6", "version": "0.1.6",
@@ -10759,9 +10692,9 @@
"dev": true "dev": true
}, },
"is-docker": { "is-docker": {
"version": "2.1.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"dev": true, "dev": true,
"optional": true "optional": true
}, },
@@ -10890,9 +10823,9 @@
} }
}, },
"is-number-object": { "is-number-object": {
"version": "1.0.4", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz",
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw=="
}, },
"is-obj": { "is-obj": {
"version": "2.0.0", "version": "2.0.0",
@@ -10954,18 +10887,18 @@
"optional": true "optional": true
}, },
"is-potential-custom-element-name": { "is-potential-custom-element-name": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
"integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
"dev": true "dev": true
}, },
"is-regex": { "is-regex": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz",
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==",
"requires": { "requires": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
"has-symbols": "^1.0.1" "has-symbols": "^1.0.2"
} }
}, },
"is-resolvable": { "is-resolvable": {
@@ -10994,25 +10927,26 @@
"dev": true "dev": true
}, },
"is-string": { "is-string": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz",
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w=="
}, },
"is-svg": { "is-svg": {
"version": "3.0.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-4.3.1.tgz",
"integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", "integrity": "sha512-h2CGs+yPUyvkgTJQS9cJzo9lYK06WgRiXUqBBHtglSzVKAuH4/oWsqk7LGfbSa1hGk9QcZ0SyQtVggvBA8LZXA==",
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"html-comment-regex": "^1.1.0" "fast-xml-parser": "^3.19.0"
} }
}, },
"is-symbol": { "is-symbol": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
"requires": { "requires": {
"has-symbols": "^1.0.1" "has-symbols": "^1.0.2"
} }
}, },
"is-typedarray": { "is-typedarray": {
@@ -11583,6 +11517,19 @@
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true "dev": true
}, },
"escodegen": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
"integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
"dev": true,
"requires": {
"esprima": "^4.0.1",
"estraverse": "^4.2.0",
"esutils": "^2.0.2",
"optionator": "^0.8.1",
"source-map": "~0.6.1"
}
},
"expect": { "expect": {
"version": "24.9.0", "version": "24.9.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz",
@@ -12143,9 +12090,9 @@
"dev": true "dev": true
}, },
"node-notifier": { "node-notifier": {
"version": "5.4.3", "version": "5.4.5",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz",
"integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"growly": "^1.3.0", "growly": "^1.3.0",
@@ -12273,9 +12220,9 @@
"dev": true "dev": true
}, },
"stack-utils": { "stack-utils": {
"version": "1.0.4", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.4.tgz", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz",
"integrity": "sha512-IPDJfugEGbfizBwBZRZ3xpccMdRyP5lqsBWXGQWimVjua/ccLCeMOAVjlc1R7LxFjo5sEDhyNIXd8mo/AiDS9w==", "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"escape-string-regexp": "^2.0.0" "escape-string-regexp": "^2.0.0"
@@ -12596,9 +12543,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -12642,13 +12589,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"slash": { "slash": {
@@ -12699,9 +12646,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -12772,9 +12719,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -12871,9 +12818,9 @@
}, },
"dependencies": { "dependencies": {
"anymatch": { "anymatch": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true, "dev": true,
"requires": { "requires": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
@@ -12912,13 +12859,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"to-regex-range": { "to-regex-range": {
@@ -12968,9 +12915,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13041,9 +12988,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13118,9 +13065,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13164,13 +13111,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"slash": { "slash": {
@@ -13247,9 +13194,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13374,9 +13321,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13460,9 +13407,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13557,9 +13504,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13597,9 +13544,9 @@
} }
}, },
"semver": { "semver": {
"version": "7.3.4", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@@ -13655,9 +13602,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13701,13 +13648,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.1",
"picomatch": "^2.0.5" "picomatch": "^2.2.3"
} }
}, },
"supports-color": { "supports-color": {
@@ -13754,9 +13701,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13820,9 +13767,9 @@
} }
}, },
"chalk": { "chalk": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -13916,37 +13863,45 @@
"dev": true "dev": true
}, },
"jsdom": { "jsdom": {
"version": "16.4.0", "version": "16.5.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.5.3.tgz",
"integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", "integrity": "sha512-Qj1H+PEvUsOtdPJ056ewXM4UJPCi4hhLA8wpiz9F2YvsRBhuFsXxtrIFAgGBDynQA9isAMGE91PfUYbdMPXuTA==",
"dev": true, "dev": true,
"requires": { "requires": {
"abab": "^2.0.3", "abab": "^2.0.5",
"acorn": "^7.1.1", "acorn": "^8.1.0",
"acorn-globals": "^6.0.0", "acorn-globals": "^6.0.0",
"cssom": "^0.4.4", "cssom": "^0.4.4",
"cssstyle": "^2.2.0", "cssstyle": "^2.3.0",
"data-urls": "^2.0.0", "data-urls": "^2.0.0",
"decimal.js": "^10.2.0", "decimal.js": "^10.2.1",
"domexception": "^2.0.1", "domexception": "^2.0.1",
"escodegen": "^1.14.1", "escodegen": "^2.0.0",
"html-encoding-sniffer": "^2.0.1", "html-encoding-sniffer": "^2.0.1",
"is-potential-custom-element-name": "^1.0.0", "is-potential-custom-element-name": "^1.0.0",
"nwsapi": "^2.2.0", "nwsapi": "^2.2.0",
"parse5": "5.1.1", "parse5": "6.0.1",
"request": "^2.88.2", "request": "^2.88.2",
"request-promise-native": "^1.0.8", "request-promise-native": "^1.0.9",
"saxes": "^5.0.0", "saxes": "^5.0.1",
"symbol-tree": "^3.2.4", "symbol-tree": "^3.2.4",
"tough-cookie": "^3.0.1", "tough-cookie": "^4.0.0",
"w3c-hr-time": "^1.0.2", "w3c-hr-time": "^1.0.2",
"w3c-xmlserializer": "^2.0.0", "w3c-xmlserializer": "^2.0.0",
"webidl-conversions": "^6.1.0", "webidl-conversions": "^6.1.0",
"whatwg-encoding": "^1.0.5", "whatwg-encoding": "^1.0.5",
"whatwg-mimetype": "^2.3.0", "whatwg-mimetype": "^2.3.0",
"whatwg-url": "^8.0.0", "whatwg-url": "^8.5.0",
"ws": "^7.2.3", "ws": "^7.4.4",
"xml-name-validator": "^3.0.0" "xml-name-validator": "^3.0.0"
},
"dependencies": {
"acorn": {
"version": "8.2.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz",
"integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==",
"dev": true
}
} }
}, },
"jsesc": { "jsesc": {
@@ -14215,9 +14170,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.20", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"lodash-es": { "lodash-es": {
"version": "4.17.21", "version": "4.17.21",
@@ -14401,9 +14356,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -14784,9 +14739,9 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
@@ -14798,18 +14753,18 @@
"dev": true "dev": true
}, },
"mime-db": { "mime-db": {
"version": "1.45.0", "version": "1.47.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
"integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
"dev": true "dev": true
}, },
"mime-types": { "mime-types": {
"version": "2.1.28", "version": "2.1.30",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
"integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
"dev": true, "dev": true,
"requires": { "requires": {
"mime-db": "1.45.0" "mime-db": "1.47.0"
} }
}, },
"mimic-fn": { "mimic-fn": {
@@ -15116,9 +15071,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -15253,9 +15208,9 @@
"dev": true "dev": true
}, },
"node-notifier": { "node-notifier": {
"version": "8.0.1", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz",
"integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", "integrity": "sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
@@ -15278,9 +15233,9 @@
} }
}, },
"semver": { "semver": {
"version": "7.3.4", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
@@ -15314,9 +15269,9 @@
} }
}, },
"node-releases": { "node-releases": {
"version": "1.1.70", "version": "1.1.71",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
"integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
"dev": true "dev": true
}, },
"normalize-package-data": { "normalize-package-data": {
@@ -15463,16 +15418,16 @@
"integrity": "sha1-rwt5f/6+r4pSxmN87b6IFs/sG8g=" "integrity": "sha1-rwt5f/6+r4pSxmN87b6IFs/sG8g="
}, },
"object-inspect": { "object-inspect": {
"version": "1.9.0", "version": "1.10.3",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz",
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw=="
}, },
"object-is": { "object-is": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz",
"integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==",
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
@@ -15516,100 +15471,29 @@
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1", "es-abstract": "^1.18.0-next.1",
"has": "^1.0.3" "has": "^1.0.3"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"object.fromentries": { "object.fromentries": {
"version": "2.0.3", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.3.tgz", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz",
"integrity": "sha512-IDUSMXs6LOSJBWE++L0lzIbSqHl9KDCfff2x/JSEIDtEUavUnyMYC2ZGay/04Zq4UT8lvd4xNhU4/YHKibAOlw==", "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1", "es-abstract": "^1.18.0-next.2",
"has": "^1.0.3" "has": "^1.0.3"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"object.getownpropertydescriptors": { "object.getownpropertydescriptors": {
"version": "2.1.1", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz",
"integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1" "es-abstract": "^1.18.0-next.2"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"object.pick": { "object.pick": {
@@ -15622,39 +15506,15 @@
} }
}, },
"object.values": { "object.values": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz",
"integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1", "es-abstract": "^1.18.0-next.2",
"has": "^1.0.3" "has": "^1.0.3"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"obuf": { "obuf": {
@@ -15996,9 +15856,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -16049,9 +15909,9 @@
"integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=" "integrity": "sha1-8r0iH2zJcKk42IVWq8WJyqqiveE="
}, },
"parse5": { "parse5": {
"version": "5.1.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
"dev": true "dev": true
}, },
"parseurl": { "parseurl": {
@@ -16071,9 +15931,9 @@
}, },
"dependencies": { "dependencies": {
"tslib": { "tslib": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true "dev": true
} }
} }
@@ -16140,9 +16000,9 @@
"dev": true "dev": true
}, },
"pbkdf2": { "pbkdf2": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
"integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"dev": true, "dev": true,
"requires": { "requires": {
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
@@ -16166,9 +16026,9 @@
"dev": true "dev": true
}, },
"picomatch": { "picomatch": {
"version": "2.2.2", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==",
"dev": true "dev": true
}, },
"pify": { "pify": {
@@ -16981,24 +16841,21 @@
} }
}, },
"postcss-selector-parser": { "postcss-selector-parser": {
"version": "6.0.4", "version": "6.0.6",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz",
"integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==",
"dev": true, "dev": true,
"requires": { "requires": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"indexes-of": "^1.0.1",
"uniq": "^1.0.1",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
} }
}, },
"postcss-svgo": { "postcss-svgo": {
"version": "4.0.2", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz",
"integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-svg": "^3.0.0",
"postcss": "^7.0.0", "postcss": "^7.0.0",
"postcss-value-parser": "^3.0.0", "postcss-value-parser": "^3.0.0",
"svgo": "^1.0.0" "svgo": "^1.0.0"
@@ -17088,9 +16945,9 @@
"dev": true "dev": true
}, },
"react-is": { "react-is": {
"version": "17.0.1", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true "dev": true
} }
} }
@@ -17138,9 +16995,9 @@
"dev": true "dev": true
}, },
"prompts": { "prompts": {
"version": "2.4.0", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz",
"integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"kleur": "^3.0.3", "kleur": "^3.0.3",
@@ -17227,17 +17084,17 @@
}, },
"dependencies": { "dependencies": {
"bn.js": { "bn.js": {
"version": "4.11.9", "version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
"dev": true "dev": true
} }
} }
}, },
"pubsub-js": { "pubsub-js": {
"version": "1.7.0", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.7.0.tgz", "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.9.3.tgz",
"integrity": "sha512-Pb68P9qFZxnvDipHMuj9oT1FoIgBcXJ9C9eWdHCLZAnulaUoJ3+Y87RhGMYilWpun6DMWVmvK70T4RP4drZMSA==" "integrity": "sha512-FhYYlPNOywTh7zN38u5AlG67emA47w6JZd7YgdQU1w8gQbZhhIGxVM0AQosdaINHb2ALb+fhfnVyBJAt4D4IzA=="
}, },
"pump": { "pump": {
"version": "3.0.0", "version": "3.0.0",
@@ -17291,11 +17148,12 @@
"dev": true "dev": true
}, },
"query-string": { "query-string": {
"version": "6.13.8", "version": "6.14.1",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.8.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.14.1.tgz",
"integrity": "sha512-jxJzQI2edQPE/NPUOusNjO/ZOGqr1o2OBa/3M00fU76FsLXDVbJDv/p7ng5OdQyorKrkRz1oqfwmbe5MAMePQg==", "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
"requires": { "requires": {
"decode-uri-component": "^0.2.0", "decode-uri-component": "^0.2.0",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0", "split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0" "strict-uri-encode": "^2.0.0"
} }
@@ -17318,6 +17176,12 @@
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"dev": true "dev": true
}, },
"queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true
},
"randombytes": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -17366,9 +17230,9 @@
} }
}, },
"react-bootstrap": { "react-bootstrap": {
"version": "1.5.2", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.6.0.tgz",
"integrity": "sha512-mGKPY5+lLd7Vtkx2VFivoRkPT4xAHazuFfIhJLTEgHlDfIUSePn7qrmpZe5gXH9zvHV0RsBaQ9cLfXjxnZrOpA==", "integrity": "sha512-PaeOGeRC2+JH9Uf1PukJgXcIpfGlrKKHEBZIArymjenYzSJ/RhO2UdNX+e7nalsCFFZLRRgQ0/FKkscW2LmmRg==",
"requires": { "requires": {
"@babel/runtime": "^7.13.8", "@babel/runtime": "^7.13.8",
"@restart/context": "^2.1.4", "@restart/context": "^2.1.4",
@@ -17388,16 +17252,6 @@
"react-transition-group": "^4.4.1", "react-transition-group": "^4.4.1",
"uncontrollable": "^7.2.1", "uncontrollable": "^7.2.1",
"warning": "^4.0.3" "warning": "^4.0.3"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.17",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
} }
}, },
"react-break": { "react-break": {
@@ -17744,9 +17598,9 @@
"dev": true "dev": true
}, },
"react-fast-compare": { "react-fast-compare": {
"version": "2.0.4", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
}, },
"react-focus-lock": { "react-focus-lock": {
"version": "2.5.0", "version": "2.5.0",
@@ -17783,6 +17637,13 @@
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-fast-compare": "^2.0.4", "react-fast-compare": "^2.0.4",
"react-side-effect": "^2.1.0" "react-side-effect": "^2.1.0"
},
"dependencies": {
"react-fast-compare": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
"integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
}
} }
}, },
"react-intl": { "react-intl": {
@@ -17820,16 +17681,6 @@
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"uncontrollable": "^7.2.1", "uncontrollable": "^7.2.1",
"warning": "^4.0.3" "warning": "^4.0.3"
},
"dependencies": {
"@babel/runtime": {
"version": "7.13.17",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.17.tgz",
"integrity": "sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
} }
}, },
"react-popper": { "react-popper": {
@@ -17839,13 +17690,6 @@
"requires": { "requires": {
"react-fast-compare": "^3.0.1", "react-fast-compare": "^3.0.1",
"warning": "^4.0.2" "warning": "^4.0.2"
},
"dependencies": {
"react-fast-compare": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
}
} }
}, },
"react-proptype-conditional-require": { "react-proptype-conditional-require": {
@@ -17854,11 +17698,12 @@
"integrity": "sha1-acLVdB5t9eCPIw82u8KUTuEiJVU=" "integrity": "sha1-acLVdB5t9eCPIw82u8KUTuEiJVU="
}, },
"react-redux": { "react-redux": {
"version": "7.2.2", "version": "7.2.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
"integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==", "integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
"requires": { "requires": {
"@babel/runtime": "^7.12.1", "@babel/runtime": "^7.12.1",
"@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
@@ -17866,9 +17711,9 @@
} }
}, },
"react-remove-scroll": { "react-remove-scroll": {
"version": "2.4.1", "version": "2.4.2",
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.1.tgz", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.4.2.tgz",
"integrity": "sha512-K7XZySEzOHMTq7dDwcHsZA6Y7/1uX5RsWhRXVYv8rdh+y9Qz2nMwl9RX/Mwnj/j7JstCGmxyfyC0zbVGXYh3mA==", "integrity": "sha512-mMSIZYQF3jS2uRJXeFDRaVGA+BGs/hIryV64YUKsHFtpgwZloOUcdu0oW8K6OU8uLHt/kM5d0lUZbdpIVwgXtQ==",
"requires": { "requires": {
"react-remove-scroll-bar": "^2.1.0", "react-remove-scroll-bar": "^2.1.0",
"react-style-singleton": "^2.1.0", "react-style-singleton": "^2.1.0",
@@ -18251,9 +18096,9 @@
"dev": true "dev": true
}, },
"regjsparser": { "regjsparser": {
"version": "0.6.7", "version": "0.6.9",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz",
"integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"jsesc": "~0.5.0" "jsesc": "~0.5.0"
@@ -18327,9 +18172,9 @@
}, },
"dependencies": { "dependencies": {
"domelementtype": { "domelementtype": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"dev": true "dev": true
} }
} }
@@ -18393,9 +18238,9 @@
} }
}, },
"repeat-element": { "repeat-element": {
"version": "1.1.3", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
"integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==",
"dev": true "dev": true
}, },
"repeat-string": { "repeat-string": {
@@ -18516,12 +18361,12 @@
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
}, },
"resolve": { "resolve": {
"version": "1.19.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-core-module": "^2.1.0", "is-core-module": "^2.2.0",
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
}, },
@@ -18817,10 +18662,13 @@
"dev": true "dev": true
}, },
"run-parallel": { "run-parallel": {
"version": "1.1.10", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true "dev": true,
"requires": {
"queue-microtask": "^1.2.2"
}
}, },
"run-queue": { "run-queue": {
"version": "1.0.3", "version": "1.0.3",
@@ -18838,9 +18686,9 @@
"dev": true "dev": true
}, },
"rxjs": { "rxjs": {
"version": "6.6.3", "version": "6.6.7",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
"integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
@@ -18926,9 +18774,9 @@
} }
}, },
"semver": { "semver": {
"version": "7.3.4", "version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
"integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
@@ -19003,9 +18851,9 @@
"dev": true "dev": true
}, },
"selfsigned": { "selfsigned": {
"version": "1.10.8", "version": "1.10.11",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.11.tgz",
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "integrity": "sha512-aVmbPOfViZqOZPgRBT0+3u4yZFHpmnIghLMlAcb5/xhp5ZtB/RVnKhz5vl2M32CLXAqR4kha9zfhNg0Lf/sxKA==",
"dev": true, "dev": true,
"requires": { "requires": {
"node-forge": "^0.10.0" "node-forge": "^0.10.0"
@@ -19749,9 +19597,9 @@
} }
}, },
"ssri": { "ssri": {
"version": "6.0.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz",
"integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"figgy-pudding": "^3.5.1" "figgy-pudding": "^3.5.1"
@@ -19949,9 +19797,9 @@
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
}, },
"string-length": { "string-length": {
"version": "4.0.1", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
"integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"char-regex": "^1.0.2", "char-regex": "^1.0.2",
@@ -19970,9 +19818,9 @@
} }
}, },
"string-width": { "string-width": {
"version": "4.2.0", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
"dev": true, "dev": true,
"requires": { "requires": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
@@ -19992,59 +19840,35 @@
} }
}, },
"string.prototype.matchall": { "string.prototype.matchall": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.3.tgz", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.4.tgz",
"integrity": "sha512-OBxYDA2ifZQ2e13cP82dWFMaCV9CGF8GzmN4fljBVw5O5wep0lu4gacm1OL6MjROoUnB8VbkWRThqkV2YFLNxw==", "integrity": "sha512-pknFIWVachNcyqRfaQSeu/FUfpvJTe4uskUSZ9Wc1RijsPuzbZ8TyYT8WCNnntCjUEqQ3vUHMAfVj2+wLAisPQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3", "define-properties": "^1.1.3",
"es-abstract": "^1.18.0-next.1", "es-abstract": "^1.18.0-next.2",
"has-symbols": "^1.0.1", "has-symbols": "^1.0.1",
"internal-slot": "^1.0.2", "internal-slot": "^1.0.3",
"regexp.prototype.flags": "^1.3.0", "regexp.prototype.flags": "^1.3.1",
"side-channel": "^1.0.3" "side-channel": "^1.0.4"
},
"dependencies": {
"es-abstract": {
"version": "1.18.0-next.2",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz",
"integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==",
"dev": true,
"requires": {
"call-bind": "^1.0.2",
"es-to-primitive": "^1.2.1",
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2",
"has": "^1.0.3",
"has-symbols": "^1.0.1",
"is-callable": "^1.2.2",
"is-negative-zero": "^2.0.1",
"is-regex": "^1.1.1",
"object-inspect": "^1.9.0",
"object-keys": "^1.1.1",
"object.assign": "^4.1.2",
"string.prototype.trimend": "^1.0.3",
"string.prototype.trimstart": "^1.0.3"
}
}
} }
}, },
"string.prototype.trimend": { "string.prototype.trimend": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
"integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
"string.prototype.trimstart": { "string.prototype.trimstart": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
"integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
"requires": { "requires": {
"call-bind": "^1.0.0", "call-bind": "^1.0.2",
"define-properties": "^1.1.3" "define-properties": "^1.1.3"
} }
}, },
@@ -20269,9 +20093,9 @@
} }
}, },
"supports-hyperlinks": { "supports-hyperlinks": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz",
"integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"has-flag": "^4.0.0", "has-flag": "^4.0.0",
@@ -20351,9 +20175,9 @@
}, },
"dependencies": { "dependencies": {
"domelementtype": { "domelementtype": {
"version": "2.1.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz",
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==",
"dev": true "dev": true
} }
} }
@@ -20667,16 +20491,24 @@
} }
}, },
"teeny-request": { "teeny-request": {
"version": "6.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-6.0.1.tgz", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.1.tgz",
"integrity": "sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==", "integrity": "sha512-sasJmQ37klOlplL4Ia/786M5YlOcoLGQyq2TE4WHSRupbAuDaQW0PfVxV4MtdBtRJ4ngzS+1qim8zP6Zp35qCw==",
"dev": true, "dev": true,
"requires": { "requires": {
"http-proxy-agent": "^4.0.0", "http-proxy-agent": "^4.0.0",
"https-proxy-agent": "^4.0.0", "https-proxy-agent": "^5.0.0",
"node-fetch": "^2.2.0", "node-fetch": "^2.6.1",
"stream-events": "^1.0.5", "stream-events": "^1.0.5",
"uuid": "^3.3.2" "uuid": "^8.0.0"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true
}
} }
}, },
"temp-dir": { "temp-dir": {
@@ -20960,14 +20792,22 @@
"dev": true "dev": true
}, },
"tough-cookie": { "tough-cookie": {
"version": "3.0.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"dev": true, "dev": true,
"requires": { "requires": {
"ip-regex": "^2.1.0", "psl": "^1.1.33",
"psl": "^1.1.28", "punycode": "^2.1.1",
"punycode": "^2.1.1" "universalify": "^0.1.2"
},
"dependencies": {
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"dev": true
}
} }
}, },
"tr46": { "tr46": {
@@ -21113,9 +20953,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.1.3", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz",
"integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==", "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==",
"dev": true "dev": true
}, },
"unbox-primitive": { "unbox-primitive": {
@@ -21127,13 +20967,6 @@
"has-bigints": "^1.0.1", "has-bigints": "^1.0.1",
"has-symbols": "^1.0.2", "has-symbols": "^1.0.2",
"which-boxed-primitive": "^1.0.2" "which-boxed-primitive": "^1.0.2"
},
"dependencies": {
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
}
} }
}, },
"unbzip2-stream": { "unbzip2-stream": {
@@ -21365,9 +21198,9 @@
} }
}, },
"url-parse": { "url-parse": {
"version": "1.4.7", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
@@ -21470,15 +21303,15 @@
"dev": true "dev": true
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.2.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
"integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
"dev": true "dev": true
}, },
"v8-to-istanbul": { "v8-to-istanbul": {
"version": "7.1.0", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz",
"integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/istanbul-lib-coverage": "^2.0.1", "@types/istanbul-lib-coverage": "^2.0.1",
@@ -21586,9 +21419,9 @@
}, },
"dependencies": { "dependencies": {
"anymatch": { "anymatch": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
@@ -21648,9 +21481,9 @@
"optional": true "optional": true
}, },
"glob-parent": { "glob-parent": {
"version": "5.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "dev": true,
"optional": true, "optional": true,
"requires": { "requires": {
@@ -22032,9 +21865,9 @@
}, },
"dependencies": { "dependencies": {
"mime": { "mime": {
"version": "2.5.0", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
"integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==", "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==",
"dev": true "dev": true
} }
} }
@@ -22396,12 +22229,12 @@
"dev": true "dev": true
}, },
"whatwg-url": { "whatwg-url": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz",
"integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==",
"dev": true, "dev": true,
"requires": { "requires": {
"lodash.sortby": "^4.7.0", "lodash": "^4.7.0",
"tr46": "^2.0.2", "tr46": "^2.0.2",
"webidl-conversions": "^6.1.0" "webidl-conversions": "^6.1.0"
} }
@@ -22564,9 +22397,9 @@
} }
}, },
"ws": { "ws": {
"version": "7.4.3", "version": "7.4.5",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
"integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
"dev": true "dev": true
}, },
"xml-name-validator": { "xml-name-validator": {
@@ -22597,9 +22430,9 @@
"dev": true "dev": true
}, },
"y18n": { "y18n": {
"version": "4.0.1", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true "dev": true
}, },
"yallist": { "yallist": {
@@ -22610,9 +22443,9 @@
"optional": true "optional": true
}, },
"yaml": { "yaml": {
"version": "1.10.0", "version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true "dev": true
}, },
"yargs": { "yargs": {

View File

@@ -38,7 +38,8 @@
"@edx/brand": "npm:@edx/brand-openedx@1.1.0", "@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "10.1.4", "@edx/frontend-component-footer": "10.1.4",
"@edx/frontend-enterprise": "4.2.3", "@edx/frontend-enterprise": "4.2.3",
"@edx/frontend-platform": "1.8.4", "@edx/frontend-lib-special-exams": "1.0.0",
"@edx/frontend-platform": "1.11.0",
"@edx/paragon": "14.8.0", "@edx/paragon": "14.8.0",
"@fortawesome/fontawesome-svg-core": "1.2.34", "@fortawesome/fontawesome-svg-core": "1.2.34",
"@fortawesome/free-brands-svg-icons": "5.13.1", "@fortawesome/free-brands-svg-icons": "5.13.1",
@@ -55,7 +56,7 @@
"react-break": "1.3.2", "react-break": "1.3.2",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-helmet": "6.0.0", "react-helmet": "6.0.0",
"react-redux": "7.2.2", "react-redux": "7.2.4",
"react-router": "5.2.0", "react-router": "5.2.0",
"react-router-dom": "5.2.0", "react-router-dom": "5.2.0",
"react-share": "4.2.1", "react-share": "4.2.1",
@@ -71,9 +72,9 @@
"@testing-library/react": "10.3.0", "@testing-library/react": "10.3.0",
"@testing-library/user-event": "12.0.17", "@testing-library/user-event": "12.0.17",
"axios-mock-adapter": "1.18.2", "axios-mock-adapter": "1.18.2",
"codecov": "3.8.1", "codecov": "3.8.2",
"es-check": "5.1.4", "es-check": "5.1.4",
"glob": "7.1.6", "glob": "7.1.7",
"husky": "3.1.0", "husky": "3.1.0",
"jest": "24.9.0", "jest": "24.9.0",
"jest-chain": "1.1.5", "jest-chain": "1.1.5",

View File

@@ -0,0 +1,135 @@
import React, { useState } from 'react';
import Cookies from 'js-cookie';
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import {
AlertModal,
Button,
Spinner,
Icon,
} from '@edx/paragon';
import { Check, ArrowForward } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl } from '@edx/frontend-platform/i18n';
import { sendActivationEmail } from '../../courseware/data';
function AccountActivationAlert() {
const [showModal, setShowModal] = useState(false);
const [showSpinner, setShowSpinner] = useState(false);
const [showCheck, setShowCheck] = useState(false);
const handleOnClick = () => {
setShowSpinner(true);
setShowCheck(false);
sendActivationEmail().then(() => {
setShowSpinner(false);
setShowCheck(true);
});
};
const showAccountActivationAlert = Cookies.get('show-account-activation-popup');
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
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"
className=""
onClick={() => setShowModal(false)}
>
<FormattedMessage
id="account-activation.alert.button"
defaultMessage="Continue to {siteName}"
description="account activation alert continue button"
values={{
siteName: getConfig().SITE_NAME,
}}
/>
<Icon src={ArrowForward} className="ml-1 d-inline-block align-bottom" />
</Button>
);
const children = () => {
let bodyContent = null;
const message = (
<FormattedMessage
id="account-activation.alert.message"
defaultMessage="We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or
{sendEmailTag}."
description="Message for account activation alert which is shown after the registration"
values={{
boldEmail: <b>{getAuthenticatedUser() && getAuthenticatedUser().email}</b>,
sendEmailTag: (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a href="#" role="button" onClick={handleOnClick}>
<FormattedMessage
id="account-activation.resend.link"
defaultMessage="resend the email"
description="Message for resend link in account activation alert which is shown after the registration"
/>
</a>
),
}}
/>
);
bodyContent = (
<div>
{message}
</div>
);
if (!showCheck && showSpinner) {
bodyContent = (
<div>
{message}
<Spinner
animation="border"
variant="secondary"
style={{ height: '1.5rem', width: '1.5rem' }}
/>
</div>
);
}
if (showCheck && !showSpinner) {
bodyContent = (
<div>
{message}
<Icon
src={Check}
style={{ height: '1.7rem', width: '1.25rem' }}
className="text-success-500 d-inline-block position-fixed"
/>
</div>
);
}
return bodyContent;
};
return (
<AlertModal
isOpen={showModal}
title={title}
footerNode={button}
onClose={() => ({})}
>
{children()}
</AlertModal>
);
}
export default injectIntl(AccountActivationAlert);

View File

@@ -31,6 +31,12 @@ Factory.define('outlineTabData')
.attrs({ .attrs({
access_expiration: null, access_expiration: null,
can_show_upgrade_sock: false, can_show_upgrade_sock: false,
cert_data: {
cert_status: null,
cert_web_view_url: null,
certificate_available_date: null,
download_url: null,
},
course_goals: { course_goals: {
goal_options: [], goal_options: [],
selected_goal: null, selected_goal: null,

View File

@@ -12,9 +12,9 @@ Factory.define('progressTabData')
locked_count: 0, locked_count: 0,
}, },
course_grade: { course_grade: {
letter_grade: null, letter_grade: 'pass',
percent: 0, percent: 1,
is_passing: false, is_passing: true,
}, },
section_scores: [ section_scores: [
{ {
@@ -22,6 +22,7 @@ Factory.define('progressTabData')
subsections: [ subsections: [
{ {
assignment_type: 'Homework', assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection', display_name: 'First subsection',
has_graded_assignment: true, has_graded_assignment: true,
num_points_earned: 0, num_points_earned: 0,
@@ -55,6 +56,7 @@ Factory.define('progressTabData')
assignment_policies: [ assignment_policies: [
{ {
num_droppable: 1, num_droppable: 1,
num_total: 2,
short_label: 'HW', short_label: 'HW',
type: 'Homework', type: 'Homework',
weight: 1, weight: 1,

View File

@@ -12,8 +12,10 @@ Object {
"courseware": Object { "courseware": Object {
"courseId": null, "courseId": null,
"courseStatus": "loading", "courseStatus": "loading",
"proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null, "sequenceId": null,
"sequenceStatus": "loading", "sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
}, },
"models": Object { "models": Object {
"courseHomeMeta": Object { "courseHomeMeta": Object {
@@ -305,8 +307,10 @@ Object {
"courseware": Object { "courseware": Object {
"courseId": null, "courseId": null,
"courseStatus": "loading", "courseStatus": "loading",
"proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null, "sequenceId": null,
"sequenceStatus": "loading", "sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
}, },
"models": Object { "models": Object {
"courseHomeMeta": Object { "courseHomeMeta": Object {
@@ -363,6 +367,12 @@ Object {
"course-v1:edX+DemoX+Demo_Course_1": Object { "course-v1:edX+DemoX+Demo_Course_1": Object {
"accessExpiration": null, "accessExpiration": null,
"canShowUpgradeSock": false, "canShowUpgradeSock": false,
"certData": Object {
"certStatus": null,
"certWebViewUrl": null,
"certificateAvailableDate": null,
"downloadUrl": null,
},
"courseBlocks": Object { "courseBlocks": Object {
"courses": Object { "courses": Object {
"block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object { "block-v1:edX+DemoX+Demo_Course+type@course+block@bcdabcdabcdabcdabcdabcdabcdabcd3": Object {
@@ -460,3 +470,157 @@ Object {
}, },
} }
`; `;
exports[`Data layer integration tests Test fetchProgressTab Should fetch, normalize, and save metadata 1`] = `
Object {
"courseHome": Object {
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
"courseStatus": "loaded",
"toastBodyLink": null,
"toastBodyText": null,
"toastHeader": "",
},
"courseware": Object {
"courseId": null,
"courseStatus": "loading",
"proctoredExamsEnabledWaffleFlag": false,
"sequenceId": null,
"sequenceStatus": "loading",
"specialExamsEnabledWaffleFlag": false,
},
"models": Object {
"courseHomeMeta": Object {
"course-v1:edX+DemoX+Demo_Course_1": Object {
"canLoadCourseware": false,
"id": "course-v1:edX+DemoX+Demo_Course_1",
"isEnrolled": false,
"isSelfPaced": false,
"isStaff": false,
"number": "DemoX",
"org": "edX",
"originalUserIsStaff": false,
"tabs": Array [
Object {
"slug": "outline",
"title": "Course",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/course/",
},
Object {
"slug": "discussion",
"title": "Discussion",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/discussion/forum/",
},
Object {
"slug": "wiki",
"title": "Wiki",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/course_wiki",
},
Object {
"slug": "progress",
"title": "Progress",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/progress",
},
Object {
"slug": "instructor",
"title": "Instructor",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/instructor",
},
Object {
"slug": "dates",
"title": "Dates",
"url": "http://localhost:18000/courses/course-v1:edX+DemoX+Demo_Course_1/dates",
},
],
"title": "Demonstration Course",
"verifiedMode": Object {
"currencySymbol": "$",
"price": 10,
"upgradeUrl": "test",
},
},
},
"progress": Object {
"course-v1:edX+DemoX+Demo_Course_1": Object {
"certificateData": Object {},
"completionSummary": Object {
"completeCount": 1,
"incompleteCount": 1,
"lockedCount": 0,
},
"courseGrade": Object {
"isPassing": true,
"letterGrade": "pass",
"percent": 1,
"visiblePercent": 1,
},
"courseId": "course-v1:edX+DemoX+Demo_Course_1",
"end": "3027-03-31T00:00:00Z",
"enrollmentMode": "audit",
"gradingPolicy": Object {
"assignmentPolicies": Array [
Object {
"averageGrade": 1,
"numDroppable": 1,
"shortLabel": "HW",
"type": "Homework",
"weight": 1,
"weightedGrade": 1,
},
],
"gradeRange": Object {
"pass": 0.75,
},
},
"hasScheduledContent": false,
"id": "course-v1:edX+DemoX+Demo_Course_1",
"sectionScores": Array [
Object {
"displayName": "First section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"blockKey": "block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345",
"displayName": "First subsection",
"hasGradedAssignment": true,
"numPointsEarned": 0,
"numPointsPossible": 1,
"percentGraded": 0,
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection",
},
],
},
Object {
"displayName": "Second section",
"subsections": Array [
Object {
"assignmentType": "Homework",
"displayName": "Second subsection",
"hasGradedAssignment": true,
"numPointsEarned": 1,
"numPointsPossible": 1,
"percentGraded": 1,
"showCorrectness": "always",
"showGrades": true,
"url": "http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection",
},
],
},
],
"studioUrl": "http://studio.edx.org/settings/grading/course-v1:edX+Test+run",
"userHasPassingGrade": false,
"verificationData": Object {
"link": null,
"status": "none",
"statusDate": null,
},
"verifiedMode": null,
},
},
},
"recommendations": Object {
"recommendationsStatus": "loading",
},
}
`;

View File

@@ -3,6 +3,90 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logInfo } from '@edx/frontend-platform/logging'; import { logInfo } from '@edx/frontend-platform/logging';
import { appendBrowserTimezoneToUrl } from '../../utils'; import { appendBrowserTimezoneToUrl } from '../../utils';
const calculateAssignmentTypeGrades = (points, assignmentWeight, numDroppable) => {
let dropCount = numDroppable;
// Drop the lowest grades
while (dropCount && points.length >= dropCount) {
const lowestScore = Math.min(...points);
const lowestScoreIndex = points.indexOf(lowestScore);
points.splice(lowestScoreIndex, 1);
dropCount--;
}
let averageGrade = 0;
let weightedGrade = 0;
if (points.length) {
averageGrade = points.reduce((a, b) => a + b, 0) / points.length;
weightedGrade = averageGrade * assignmentWeight;
}
return { averageGrade, weightedGrade };
};
function normalizeAssignmentPolicies(assignmentPolicies, sectionScores) {
const gradeByAssignmentType = {};
assignmentPolicies.forEach(assignment => {
// Create an array with the number of total assignments and set the scores to 0
// as placeholders for assignments that have not yet been released
gradeByAssignmentType[assignment.type] = {
grades: Array(assignment.numTotal).fill(0),
numAssignmentsCreated: 0,
numTotalExpectedAssignments: assignment.numTotal,
};
});
sectionScores.forEach((chapter) => {
chapter.subsections.forEach((subsection) => {
if (!(subsection.hasGradedAssignment && subsection.showGrades && subsection.numPointsPossible)) {
return;
}
const {
assignmentType,
numPointsEarned,
numPointsPossible,
} = subsection;
// If a subsection's assignment type does not match an assignment policy in Studio,
// we won't be able to include it in this accumulation of grades by assignment type.
// This may happen if a course author has removed/renamed an assignment policy in Studio and
// neglected to update the subsection's of that assignment type
if (!gradeByAssignmentType[assignmentType]) {
return;
}
let {
numAssignmentsCreated,
} = gradeByAssignmentType[assignmentType];
numAssignmentsCreated++;
if (numAssignmentsCreated <= gradeByAssignmentType[assignmentType].numTotalExpectedAssignments) {
// Remove a placeholder grade so long as the number of recorded created assignments is less than the number
// of expected assignments
gradeByAssignmentType[assignmentType].grades.shift();
}
// Add the graded assignment to the list
gradeByAssignmentType[assignmentType].grades.push(numPointsEarned ? numPointsEarned / numPointsPossible : 0);
// Record the created assignment
gradeByAssignmentType[assignmentType].numAssignmentsCreated = numAssignmentsCreated;
});
});
return assignmentPolicies.map((assignment) => {
const { averageGrade, weightedGrade } = calculateAssignmentTypeGrades(
gradeByAssignmentType[assignment.type].grades,
assignment.weight,
assignment.numDroppable,
);
return {
averageGrade,
numDroppable: assignment.numDroppable,
shortLabel: assignment.shortLabel,
type: assignment.type,
weight: assignment.weight,
weightedGrade,
};
});
}
function normalizeCourseHomeCourseMetadata(metadata) { function normalizeCourseHomeCourseMetadata(metadata) {
const data = camelCaseObject(metadata); const data = camelCaseObject(metadata);
return { return {
@@ -118,8 +202,8 @@ export async function getDatesTabData(courseId) {
const { httpErrorStatus } = error && error.customAttributes; const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) { if (httpErrorStatus === 404) {
global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`); global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`);
return {};
} }
// 401 can be returned for unauthenticated users or users who are not enrolled
if (httpErrorStatus === 401) { if (httpErrorStatus === 401) {
global.location.replace(`${getConfig().BASE_URL}/course/${courseId}/home`); global.location.replace(`${getConfig().BASE_URL}/course/${courseId}/home`);
} }
@@ -127,18 +211,45 @@ export async function getDatesTabData(courseId) {
} }
} }
export async function getProgressTabData(courseId) { export async function getProgressTabData(courseId, userId) {
const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress/${courseId}`; let url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress/${courseId}`;
if (userId) {
url += `/${userId}/`;
}
try { try {
const { data } = await getAuthenticatedHttpClient().get(url); const { data } = await getAuthenticatedHttpClient().get(url);
const camelCasedData = camelCaseObject(data); const camelCasedData = camelCaseObject(data);
camelCasedData.gradingPolicy.assignmentPolicies = normalizeAssignmentPolicies(
camelCasedData.gradingPolicy.assignmentPolicies,
camelCasedData.sectionScores,
);
// Accumulate the weighted grades by assignment type to calculate the learner facing grade. The grades within
// assignmentPolicies have been filtered by what's visible to the learner.
camelCasedData.courseGrade.visiblePercent = camelCasedData.gradingPolicy.assignmentPolicies
? camelCasedData.gradingPolicy.assignmentPolicies.reduce(
(accumulator, assignment) => accumulator + assignment.weightedGrade, 0,
) : camelCasedData.courseGrade.percent;
camelCasedData.courseGrade.isPassing = camelCasedData.courseGrade.visiblePercent
>= Math.min(...Object.values(data.grading_policy.grade_range));
// We replace gradingPolicy.gradeRange with the original data to preserve the intended casing for the grade.
// For example, if a grade range key is "A", we do not want it to be camel cased (i.e. "A" would become "a")
// in order to preserve a course team's desired grade formatting.
camelCasedData.gradingPolicy.gradeRange = data.grading_policy.grade_range; camelCasedData.gradingPolicy.gradeRange = data.grading_policy.grade_range;
return camelCasedData; return camelCasedData;
} catch (error) { } catch (error) {
const { httpErrorStatus } = error && error.customAttributes; const { httpErrorStatus } = error && error.customAttributes;
if (httpErrorStatus === 404) { if (httpErrorStatus === 404) {
global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/progress`); global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/progress`);
} }
// 401 can be returned for unauthenticated users or users who are not enrolled
if (httpErrorStatus === 401) {
global.location.replace(`${getConfig().BASE_URL}/course/${courseId}/home`);
}
throw error; throw error;
} }
} }
@@ -200,6 +311,7 @@ export async function getOutlineTabData(courseId) {
const accessExpiration = camelCaseObject(data.access_expiration); const accessExpiration = camelCaseObject(data.access_expiration);
const canShowUpgradeSock = data.can_show_upgrade_sock; const canShowUpgradeSock = data.can_show_upgrade_sock;
const certData = camelCaseObject(data.cert_data);
const courseBlocks = data.course_blocks ? normalizeOutlineBlocks(courseId, data.course_blocks.blocks) : {}; const courseBlocks = data.course_blocks ? normalizeOutlineBlocks(courseId, data.course_blocks.blocks) : {};
const courseGoals = camelCaseObject(data.course_goals); const courseGoals = camelCaseObject(data.course_goals);
const courseTools = camelCaseObject(data.course_tools); const courseTools = camelCaseObject(data.course_tools);
@@ -217,6 +329,7 @@ export async function getOutlineTabData(courseId) {
return { return {
accessExpiration, accessExpiration,
canShowUpgradeSock, canShowUpgradeSock,
certData,
courseBlocks, courseBlocks,
courseGoals, courseGoals,
courseTools, courseTools,

View File

@@ -88,6 +88,35 @@ describe('Data layer integration tests', () => {
}); });
}); });
describe('Test fetchProgressTab', () => {
const progressBaseUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/progress`;
it('Should result in fetch failure if error occurs', async () => {
axiosMock.onGet(courseMetadataUrl).networkError();
axiosMock.onGet(`${progressBaseUrl}/${courseId}`).networkError();
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseHome.courseStatus).toEqual('failed');
});
it('Should fetch, normalize, and save metadata', async () => {
const progressTabData = Factory.build('progressTabData', { courseId });
const progressUrl = `${progressBaseUrl}/${courseId}`;
axiosMock.onGet(courseMetadataUrl).reply(200, courseHomeMetadata);
axiosMock.onGet(progressUrl).reply(200, progressTabData);
await executeThunk(thunks.fetchProgressTab(courseId), store.dispatch);
const state = store.getState();
expect(state.courseHome.courseStatus).toEqual('loaded');
expect(state).toMatchSnapshot();
});
});
describe('Test saveCourseGoal', () => { describe('Test saveCourseGoal', () => {
it('Should save course goal', async () => { it('Should save course goal', async () => {
const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/save_course_goal`; const goalUrl = `${getConfig().LMS_BASE_URL}/api/course_home/v1/save_course_goal`;

View File

@@ -27,12 +27,12 @@ const eventTypes = {
POST_EVENT: 'post_event', POST_EVENT: 'post_event',
}; };
export function fetchTab(courseId, tab, getTabData) { export function fetchTab(courseId, tab, getTabData, userId) {
return async (dispatch) => { return async (dispatch) => {
dispatch(fetchTabRequest({ courseId })); dispatch(fetchTabRequest({ courseId }));
Promise.allSettled([ Promise.allSettled([
getCourseHomeCourseMetadata(courseId), getCourseHomeCourseMetadata(courseId),
getTabData(courseId), getTabData(courseId, userId),
]).then(([courseHomeCourseMetadataResult, tabDataResult]) => { ]).then(([courseHomeCourseMetadataResult, tabDataResult]) => {
const fetchedCourseHomeCourseMetadata = courseHomeCourseMetadataResult.status === 'fulfilled'; const fetchedCourseHomeCourseMetadata = courseHomeCourseMetadataResult.status === 'fulfilled';
const fetchedTabData = tabDataResult.status === 'fulfilled'; const fetchedTabData = tabDataResult.status === 'fulfilled';
@@ -74,8 +74,8 @@ export function fetchDatesTab(courseId) {
return fetchTab(courseId, 'dates', getDatesTabData); return fetchTab(courseId, 'dates', getDatesTabData);
} }
export function fetchProgressTab(courseId) { export function fetchProgressTab(courseId, userId) {
return fetchTab(courseId, 'progress', getProgressTabData); return fetchTab(courseId, 'progress', getProgressTabData, userId);
} }
export function fetchOutlineTab(courseId) { export function fetchOutlineTab(courseId) {

View File

@@ -2,11 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
export default function Badge({ children, className }) { export default function Badge({ children, className, ...rest }) {
return ( return (
<span <span
className={classNames('dates-badge small ml-2', className)} className={classNames('dates-badge x-small ml-2 position-absolute', className)}
data-testid="dates-badge" data-testid="dates-badge"
{...rest}
> >
{children} {children}
</span> </span>

View File

@@ -1,4 +1,4 @@
.dates-badge { .dates-badge {
border-radius: 4px; border-radius: 4px;
padding: 2px 8px 3px 8px; padding: 1px 8px;
} }

View File

@@ -2,7 +2,12 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { FormattedDate, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import {
FormattedDate,
FormattedTime,
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Tooltip, OverlayTrigger } from '@edx/paragon'; import { Tooltip, OverlayTrigger } from '@edx/paragon';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -51,7 +56,7 @@ function Day({
{/* Content */} {/* Content */}
<div className="d-inline-block ml-3 pl-2"> <div className="d-inline-block ml-3 pl-2">
<div className="mb-1" data-testid="dates-header"> <div className="mb-1" data-testid="dates-header">
<p className="d-inline text-dark-500 font-weight-bold"> <p className="d-inline text-dark-400">
<FormattedDate <FormattedDate
/** [MM-P2P] Experiment */ /** [MM-P2P] Experiment */
value={mmp2pOverride ? mmp2p.state.upgradeDeadline : date} value={mmp2pOverride ? mmp2p.state.upgradeDeadline : date}
@@ -70,16 +75,27 @@ function Day({
? getBadgeListAndColor(new Date(mmp2p.state.upgradeDeadline), intl, item, items) ? getBadgeListAndColor(new Date(mmp2p.state.upgradeDeadline), intl, item, items)
: getBadgeListAndColor(date, intl, item, items); : getBadgeListAndColor(date, intl, item, items);
const showDueDateTime = item.dateType === 'assignment-due-date';
const showLink = item.link && isLearnerAssignment(item); const showLink = item.link && isLearnerAssignment(item);
const title = showLink ? (<u><a href={item.link} className="text-reset">{item.title}</a></u>) : item.title; const title = showLink ? (<u><a href={item.link} className="text-reset">{item.title}</a></u>) : item.title;
const available = item.learnerHasAccess && (item.link || !isLearnerAssignment(item)); const available = item.learnerHasAccess && (item.link || !isLearnerAssignment(item));
const textColor = available ? 'text-dark-500' : 'text-dark-200'; const textColor = available ? 'text-primary-700' : 'text-gray-500';
return ( return (
<div key={item.title + item.date} className={textColor} data-testid="dates-item"> <div key={item.title + item.date} className={classNames(textColor, 'small')} data-testid="dates-item">
<div> <div>
<span className="font-weight-bold small mt-1"> <span className="small">
{item.assignmentType && `${item.assignmentType}: `}{title} <span className="font-weight-bold">{item.assignmentType && `${item.assignmentType}: `}{title}</span>
{showDueDateTime && (
<span>
<span className="mx-1">due</span>
<FormattedTime
value={date}
timeZoneName="short"
{...timezoneFormatArgs}
/>
</span>
)}
</span> </span>
{itemBadges} {itemBadges}
{item.extraInfo && ( {item.extraInfo && (

View File

@@ -77,6 +77,7 @@ function getBadgeListAndColor(date, intl, item, items) {
}, },
]; ];
let color = null; // first color of any badge let color = null; // first color of any badge
const marginTopStyle = item ? { marginTop: 0 } : { marginTop: '2px' };
const badges = ( const badges = (
<> <>
{badgesInfo.map(b => { {badgesInfo.map(b => {
@@ -96,7 +97,7 @@ function getBadgeListAndColor(date, intl, item, items) {
color = b.bg; color = b.bg;
} }
return ( return (
<Badge key={b.message.id} className={classNames(b.bg, b.className)}> <Badge key={b.message.id} style={marginTopStyle} className={classNames(b.bg, b.className)}>
{b.icon && <FontAwesomeIcon icon={b.icon} className="mr-1" />} {b.icon && <FontAwesomeIcon icon={b.icon} className="mr-1" />}
{intl.formatMessage(b.message)} {intl.formatMessage(b.message)}
</Badge> </Badge>

View File

@@ -19,14 +19,14 @@ import Section from './Section';
import UpdateGoalSelector from './widgets/UpdateGoalSelector'; import UpdateGoalSelector from './widgets/UpdateGoalSelector';
import UpgradeCard from './widgets/UpgradeCard'; import UpgradeCard from './widgets/UpgradeCard';
import useAccessExpirationAlert from '../../alerts/access-expiration-alert'; import useAccessExpirationAlert from '../../alerts/access-expiration-alert';
import useCertificateAvailableAlert from './alerts/certificate-available-alert'; import useCertificateAvailableAlert from './alerts/certificate-status-alert';
import useCourseEndAlert from './alerts/course-end-alert'; import useCourseEndAlert from './alerts/course-end-alert';
import useCourseStartAlert from './alerts/course-start-alert'; import useCourseStartAlert from './alerts/course-start-alert';
import useOfferAlert from '../../alerts/offer-alert';
import usePrivateCourseAlert from './alerts/private-course-alert'; import usePrivateCourseAlert from './alerts/private-course-alert';
import { useModel } from '../../generic/model-store'; import { useModel } from '../../generic/model-store';
import WelcomeMessage from './widgets/WelcomeMessage'; import WelcomeMessage from './widgets/WelcomeMessage';
import ProctoringInfoPanel from './widgets/ProctoringInfoPanel'; import ProctoringInfoPanel from './widgets/ProctoringInfoPanel';
import AccountActivationAlert from '../../alerts/logistration-alert/AccountActivationAlert';
/** [MM-P2P] Experiment */ /** [MM-P2P] Experiment */
import { initHomeMMP2P, MMP2PFlyover } from '../../experiments/mm-p2p'; import { initHomeMMP2P, MMP2PFlyover } from '../../experiments/mm-p2p';
@@ -52,7 +52,7 @@ function OutlineTab({ intl }) {
courseGoals: { courseGoals: {
goalOptions, goalOptions,
selectedGoal, selectedGoal,
}, } = {},
datesBannerInfo, datesBannerInfo,
datesWidget: { datesWidget: {
courseDateBlocks, courseDateBlocks,
@@ -86,7 +86,6 @@ function OutlineTab({ intl }) {
}; };
// Below the course title alerts (appearing in the order listed here) // Below the course title alerts (appearing in the order listed here)
const offerAlert = useOfferAlert(courseId, offer, org, userTimezone, 'outline-course-alerts', 'course_home');
const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, 'outline-course-alerts', 'course_home'); const accessExpirationAlert = useAccessExpirationAlert(accessExpiration, courseId, org, userTimezone, 'outline-course-alerts', 'course_home');
const courseStartAlert = useCourseStartAlert(courseId); const courseStartAlert = useCourseStartAlert(courseId);
const courseEndAlert = useCourseEndAlert(courseId); const courseEndAlert = useCourseEndAlert(courseId);
@@ -125,7 +124,7 @@ function OutlineTab({ intl }) {
</div> </div>
{resumeCourseUrl && ( {resumeCourseUrl && (
<div className="col-12 col-sm-auto p-0"> <div className="col-12 col-sm-auto p-0">
<Button block href={resumeCourseUrl} onClick={() => logResumeCourseClick()}> <Button variant="brand" block href={resumeCourseUrl} onClick={() => logResumeCourseClick()}>
{hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)} {hasVisitedCourse ? intl.formatMessage(messages.resume) : intl.formatMessage(messages.start)}
</Button> </Button>
</div> </div>
@@ -133,6 +132,7 @@ function OutlineTab({ intl }) {
</div> </div>
{/** [MM-P2P] Experiment (className for optimizely trigger) */} {/** [MM-P2P] Experiment (className for optimizely trigger) */}
<div className="row course-outline-tab"> <div className="row course-outline-tab">
<AccountActivationAlert />
<div className="col-12"> <div className="col-12">
<AlertList <AlertList
topic="outline-private-alerts" topic="outline-private-alerts"
@@ -153,7 +153,6 @@ function OutlineTab({ intl }) {
...certificateAvailableAlert, ...certificateAvailableAlert,
...courseEndAlert, ...courseEndAlert,
...courseStartAlert, ...courseStartAlert,
...offerAlert,
}} }}
/> />
)} )}

View File

@@ -4,6 +4,7 @@ import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import Cookies from 'js-cookie';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { buildMinimalCourseBlocks } from '../../shared/data/__factories__/courseBlocks.factory'; import { buildMinimalCourseBlocks } from '../../shared/data/__factories__/courseBlocks.factory';
@@ -13,6 +14,7 @@ import {
import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils'; import { appendBrowserTimezoneToUrl, executeThunk } from '../../utils';
import * as thunks from '../data/thunks'; import * as thunks from '../data/thunks';
import initializeStore from '../../store'; import initializeStore from '../../store';
import { CERT_STATUS_TYPE } from './alerts/certificate-status-alert/CertificateStatusAlert';
import OutlineTab from './OutlineTab'; import OutlineTab from './OutlineTab';
initializeMockApp(); initializeMockApp();
@@ -281,13 +283,13 @@ describe('Outline Tab', () => {
], ],
}); });
await fetchAndRender(); await fetchAndRender();
expect(screen.getByRole('heading', { name: 'Upcoming Dates' })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: 'Important dates' })).toBeInTheDocument();
}); });
it('does not render when course date blocks are not populated', async () => { it('does not render when course date blocks are not populated', async () => {
setMetadata({ is_enrolled: true }); setMetadata({ is_enrolled: true });
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByRole('heading', { name: 'Upcoming Dates' })).not.toBeInTheDocument(); expect(screen.queryByRole('heading', { name: 'Important dates' })).not.toBeInTheDocument();
}); });
it('sends analytics event onClick of upgrade link', async () => { it('sends analytics event onClick of upgrade link', async () => {
@@ -671,7 +673,14 @@ describe('Outline Tab', () => {
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1); const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
setMetadata({ is_enrolled: true }); setMetadata({ is_enrolled: true });
setTabData({}, { setTabData({
cert_data: {
cert_status: CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE,
cert_web_view_url: null,
certificate_available_date: tomorrow.toISOString(),
download_url: null,
},
}, {
date_blocks: [ date_blocks: [
{ {
date_type: 'course-end-date', date_type: 'course-end-date',
@@ -686,55 +695,61 @@ describe('Outline Tab', () => {
], ],
}); });
await fetchAndRender(); await fetchAndRender();
await screen.findByText('We are working on generating course certificates.'); expect(screen.queryByText('Your grade and certificate will be ready soon!')).toBeInTheDocument();
}); });
}); });
});
describe('Offer Alert', () => { describe('Certificate (web) Complete Alert', () => {
it('sends analytics event onClick of upgrade link', async () => { it('appears', async () => {
setTabData({ const now = new Date();
offer: { const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
code: 'EDXWELCOME', setMetadata({ is_enrolled: true });
expiration_date: '2070-01-01T12:00:00Z', setTabData({
original_price: '$100', cert_data: {
discounted_price: '$85', cert_status: CERT_STATUS_TYPE.DOWNLOADABLE,
percentage: 15, cert_web_view_url: 'certificate/testuuid',
upgrade_url: 'https://example.com/upgrade', certificate_available_date: null,
download_url: null,
},
}, {
date_blocks: [
{
date_type: 'course-end-date',
date: yesterday.toISOString(),
title: 'End',
}, },
}); ],
await fetchAndRender();
expect(screen.getByRole('link', { name: 'Upgrade now' })).toBeInTheDocument();
}); });
await fetchAndRender();
expect(screen.queryByText('Congratulations! Your certificate is ready.')).toBeInTheDocument();
});
});
it('sends analytics event onClick of upgrade link', async () => { describe('Certificate (pdf) Complete Alert', () => {
setTabData({ it('appears', async () => {
offer: { const now = new Date();
code: 'EDXWELCOME', const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
expiration_date: '2070-01-01T12:00:00Z', setMetadata({ is_enrolled: true });
original_price: '$100', setTabData({
discounted_price: '$85', cert_data: {
percentage: 15, cert_status: CERT_STATUS_TYPE.DOWNLOADABLE,
upgrade_url: 'https://example.com/upgrade', cert_web_view_url: null,
certificate_available_date: null,
download_url: 'download/url',
},
}, {
date_blocks: [
{
date_type: 'course-end-date',
date: yesterday.toISOString(),
title: 'End',
}, },
}); ],
await fetchAndRender();
// Clearing after render to remove any events sent on view (ex. 'Promotion Viewed')
sendTrackEvent.mockClear();
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'welcome',
linkName: 'course_home_welcome',
linkType: 'link',
pageName: 'course_home',
});
}); });
await fetchAndRender();
expect(screen.queryByText('Congratulations! Your certificate is ready.')).toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Download my certificate' })).toBeInTheDocument();
}); });
}); });
@@ -964,4 +979,51 @@ describe('Outline Tab', () => {
}); });
}); });
}); });
describe('Accont Activation Alert', () => {
beforeEach(() => {
const intersectionObserverMock = () => ({
observe: () => null,
disconnect: () => null,
});
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
});
it('displays account activation alert if cookie is set true', async () => {
Cookies.set = jest.fn();
Cookies.get = jest.fn().mockImplementation(() => 'true');
Cookies.remove = jest.fn().mockImplementation(() => { Cookies.get = jest.fn(); });
await fetchAndRender();
expect(screen.queryByText('Activate your account so you can log back in')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'resend the email' })).toBeInTheDocument();
});
it('do not displays account activation alert if cookie is not set true', async () => {
Cookies.set = jest.fn();
Cookies.get = jest.fn();
Cookies.remove = jest.fn().mockImplementation(() => { Cookies.get = jest.fn(); });
await fetchAndRender();
expect(screen.queryByText('Activate your account so you can log back in')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'resend the email' })).not.toBeInTheDocument();
});
it('sends account activation email on clicking the resened email in account activation alert', async () => {
Cookies.set = jest.fn();
Cookies.get = jest.fn().mockImplementation(() => 'true');
Cookies.remove = jest.fn().mockImplementation(() => { Cookies.get = jest.fn(); });
await fetchAndRender();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
const resendEmailUrl = `${getConfig().LMS_BASE_URL}/api/send_account_activation_email`;
axiosMock.onPost(resendEmailUrl).reply(200, {});
const resendLink = screen.getByRole('button', { name: 'resend the email' });
fireEvent.click(resendLink);
await waitFor(() => expect(axiosMock.history.post).toHaveLength(1));
expect(axiosMock.history.post[0].url).toEqual(resendEmailUrl);
});
});
}); });

View File

@@ -1,62 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, FormattedRelative } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import { Alert, ALERT_TYPES } from '../../../../generic/user-messages';
function CertificateAvailableAlert({ payload }) {
const {
certDate,
username,
userTimezone,
} = payload;
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
return (
<Alert type={ALERT_TYPES.INFO}>
<strong>
<FormattedMessage
id="learning.outline.alert.cert.title"
defaultMessage="We are working on generating course certificates."
/>
</strong>
<br />
<FormattedMessage
id="learning.outline.alert.cert.when"
defaultMessage="If you have earned a certificate, you will be able to access it {timeRemaining}. You will also be able to view your certificates on your {profileLink}."
values={{
profileLink: (
<Hyperlink
destination={`${getConfig().LMS_BASE_URL}/u/${username}`}
>
<FormattedMessage
id="learning.outline.alert.cert.profile"
defaultMessage="Learner Profile"
/>
</Hyperlink>
),
timeRemaining: (
<FormattedRelative
key="timeRemaining"
value={certDate}
{...timezoneFormatArgs}
/>
),
}}
/>
</Alert>
);
}
CertificateAvailableAlert.propTypes = {
payload: PropTypes.shape({
certDate: PropTypes.string,
username: PropTypes.string,
userTimezone: PropTypes.string,
}).isRequired,
};
export default CertificateAvailableAlert;

View File

@@ -1,44 +0,0 @@
import React, { useMemo } from 'react';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { useAlert } from '../../../../generic/user-messages';
import { useModel } from '../../../../generic/model-store';
const CertificateAvailableAlert = React.lazy(() => import('./CertificateAvailableAlert'));
function useCertificateAvailableAlert(courseId) {
const {
isEnrolled,
} = useModel('courseHomeMeta', courseId);
const {
datesWidget: {
courseDateBlocks,
userTimezone,
},
} = useModel('outline', courseId);
const authenticatedUser = getAuthenticatedUser();
const username = authenticatedUser ? authenticatedUser.username : '';
const certBlock = courseDateBlocks.find(b => b.dateType === 'certificate-available-date');
const endBlock = courseDateBlocks.find(b => b.dateType === 'course-end-date');
const endDate = endBlock ? new Date(endBlock.date) : null;
const hasEnded = endBlock ? endDate < new Date() : false;
const isVisible = isEnrolled && certBlock && hasEnded; // only show if we're between end and cert dates
const payload = {
certDate: certBlock && certBlock.date,
username,
userTimezone,
};
useAlert(isVisible, {
code: 'clientCertificateAvailableAlert',
payload: useMemo(() => payload, Object.values(payload).sort()),
topic: 'outline-course-alerts',
});
return {
clientCertificateAvailableAlert: CertificateAvailableAlert,
};
}
export default useCertificateAvailableAlert;

View File

@@ -0,0 +1,104 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
FormattedDate,
FormattedMessage,
injectIntl,
intlShape,
} from '@edx/frontend-platform/i18n';
import { Alert, Button } from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons';
import certMessages from './messages';
import certStatusMessages from '../../../progress-tab/certificate-status/messages';
export const CERT_STATUS_TYPE = {
EARNED_NOT_AVAILABLE: 'earned_but_not_available',
DOWNLOADABLE: 'downloadable',
};
function CertificateStatusAlert({ intl, payload }) {
const {
certificateAvailableDate,
certStatusType,
courseEndDate,
certURL,
isWebCert,
userTimezone,
} = payload;
let variant = '';
if (certStatusType === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE || certStatusType === CERT_STATUS_TYPE.DOWNLOADABLE) {
variant = 'success';
}
let header = '';
let body = '';
let buttonVisible = false;
let buttonMessage = '';
if (certStatusType === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) {
const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {};
const certificateAvailableDateFormatted = <FormattedDate value={certificateAvailableDate} day="numeric" month="long" year="numeric" />;
const courseEndDateFormatted = <FormattedDate value={courseEndDate} day="numeric" month="long" year="numeric" />;
header = intl.formatMessage(certMessages.certStatusEarnedNotAvailableHeader);
body = (
<p>
<FormattedMessage
id="learning.outline.alert.cert.when"
defaultMessage="This course ended on {courseEndDateFormatted}. Final grades and certificates are
scheduled to be available after {certificateAvailableDate}."
values={{
courseEndDateFormatted,
certificateAvailableDate: certificateAvailableDateFormatted,
}}
{...timezoneFormatArgs}
/>
</p>
);
} else if (certStatusType === CERT_STATUS_TYPE.DOWNLOADABLE) {
header = intl.formatMessage(certMessages.certStatusDownloadableHeader);
if (isWebCert) {
buttonMessage = intl.formatMessage(certStatusMessages.viewableButton);
} else {
buttonMessage = intl.formatMessage(certStatusMessages.downloadableButton);
}
buttonVisible = true;
}
return (
<Alert variant={variant}>
<div className="row justify-content-between align-items-center">
<div className={buttonVisible ? '' : 'col-auto'}>
<FontAwesomeIcon icon={faCheckCircle} className="alert-icon text-success-500" />
<Alert.Heading>{header}</Alert.Heading>
{body}
</div>
{buttonVisible && (
<div className="m-auto m-lg-0 pr-lg-3">
<Button
variant="primary"
href={certURL}
>
{buttonMessage}
</Button>
</div>
)}
</div>
</Alert>
);
}
CertificateStatusAlert.propTypes = {
intl: intlShape.isRequired,
payload: PropTypes.shape({
certificateAvailableDate: PropTypes.string,
certStatusType: PropTypes.string,
courseEndDate: PropTypes.string,
certURL: PropTypes.string,
isWebCert: PropTypes.bool,
userTimezone: PropTypes.string,
}).isRequired,
};
export default injectIntl(CertificateStatusAlert);

View File

@@ -0,0 +1,77 @@
import React, { useMemo } from 'react';
import { getConfig } from '@edx/frontend-platform';
import { useAlert } from '../../../../generic/user-messages';
import { useModel } from '../../../../generic/model-store';
import { CERT_STATUS_TYPE } from './CertificateStatusAlert';
const CertificateStatusAlert = React.lazy(() => import('./CertificateStatusAlert'));
function verifyCertStatusType(status) {
// This method will only return cert statuses when we want to alert on them.
// It should be modified when we want to alert on a new status type.
if (status === CERT_STATUS_TYPE.DOWNLOADABLE) {
return CERT_STATUS_TYPE.DOWNLOADABLE;
}
if (status === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) {
return CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE;
}
return '';
}
function useCertificateStatusAlert(courseId) {
const {
isEnrolled,
} = useModel('courseHomeMeta', courseId);
const {
datesWidget: {
courseDateBlocks,
userTimezone,
},
certData,
} = useModel('outline', courseId);
const {
certStatus,
certWebViewUrl,
certificateAvailableDate,
downloadUrl,
} = certData || {};
const endBlock = courseDateBlocks.find(b => b.dateType === 'course-end-date');
const certStatusType = verifyCertStatusType(certStatus);
const isWebCert = downloadUrl === null;
let certURL = '';
if (certWebViewUrl) {
certURL = `${getConfig().LMS_BASE_URL}${certWebViewUrl}`;
} else if (downloadUrl) {
// PDF Certificate
certURL = downloadUrl;
}
const hasCertStatus = certStatusType !== '';
// Only show if there is a known cert status that we want provide status on.
const isVisible = isEnrolled && hasCertStatus;
const payload = {
certificateAvailableDate,
certURL,
certStatusType,
courseEndDate: endBlock && endBlock.date,
userTimezone,
isWebCert,
};
useAlert(isVisible, {
code: 'clientCertificateStatusAlert',
payload: useMemo(() => payload, Object.values(payload).sort()),
topic: 'outline-course-alerts',
});
return {
clientCertificateStatusAlert: CertificateStatusAlert,
};
}
export default useCertificateStatusAlert;

View File

@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
certStatusEarnedNotAvailableHeader: {
id: 'cert.alert.earned.unavailable.header',
defaultMessage: 'Your grade and certificate will be ready soon!',
description: 'Header alerting the user that their certificate will be available soon.',
},
certStatusDownloadableHeader: {
id: 'cert.alert.earned.ready.header',
defaultMessage: 'Congratulations! Your certificate is ready.',
description: 'Header alerting the user that their certificate is ready.',
},
});
export default messages;

View File

@@ -22,7 +22,7 @@ const messages = defineMessages({
}, },
dates: { dates: {
id: 'learning.outline.dates', id: 'learning.outline.dates',
defaultMessage: 'Upcoming Dates', defaultMessage: 'Important dates',
}, },
editGoal: { editGoal: {
id: 'learning.outline.editGoal', id: 'learning.outline.editGoal',

View File

@@ -10,24 +10,29 @@ import { getConfig } from '@edx/frontend-platform';
import { UpgradeButton } from '../../../generic/upgrade-button'; import { UpgradeButton } from '../../../generic/upgrade-button';
function UpsellNoFBECardContent() { function UpsellNoFBECardContent() {
const verifiedCertLink = (
<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href={`${getConfig().MARKETING_SITE_BASE_URL}/verified-certificate`}>
<FormattedMessage
id="learning.outline.widgets.upgradeCard.verifiedCertLink"
defaultMessage="verified certificate"
/>
</a>
);
return ( return (
<ul className="fa-ul upgrade-card-ul pt-0"> <ul className="fa-ul upgrade-card-ul pt-0">
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.verifiedCertLink" id="learning.outline.widgets.upgradeCard.verifiedCertMessage"
defaultMessage="Earn a {verifiedCertLink} of completion to showcase on your resume" defaultMessage="Earn a {verifiedCertLink} of completion to showcase on your resume"
values={{ values={{ verifiedCertLink }}
verifiedCertLink: (
<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href={`${getConfig().MARKETING_SITE_BASE_URL}/verified-certificate`}>verified certificate</a>
),
}}
/> />
</li> </li>
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.nonProfitMission" id="learning.outline.widgets.upgradeCard.nonProfitMission"
defaultMessage="Support our {nonProfitMission} at edX" defaultMessage="Support our {nonProfitMission} at edX"
values={{ values={{
nonProfitMission: ( nonProfitMission: (
@@ -41,54 +46,74 @@ function UpsellNoFBECardContent() {
} }
function UpsellFBEFarAwayCardContent() { function UpsellFBEFarAwayCardContent() {
const verifiedCertLink = (
<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href={`${getConfig().MARKETING_SITE_BASE_URL}/verified-certificate`}>
<FormattedMessage
id="learning.outline.widgets.upgradeCard.verifiedCertLink"
defaultMessage="verified certificate"
/>
</a>
);
const gradedAssignments = (
<span className="font-weight-bold">
<FormattedMessage
id="learning.outline.widgets.upgradeCard.gradedAssignments"
defaultMessage="graded assignments"
/>
</span>
);
const fullAccess = (
<span className="font-weight-bold">
<FormattedMessage
id="learning.upgradeCard.verifiedCertLink"
defaultMessage="Full access"
/>
</span>
);
const nonProfitMission = (
<span className="font-weight-bold">
<FormattedMessage
id="learning.upgradeCard.nonProfitMission"
defaultMessage="non-profit mission"
/>
</span>
);
return ( return (
<ul className="fa-ul upgrade-card-ul"> <ul className="fa-ul upgrade-card-ul">
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.verifiedCertLink" id="learning.outline.widgets.upgradeCard.verifiedCertMessage"
defaultMessage="Earn a {verifiedCertLink} of completion to showcase on your resume" defaultMessage="Earn a {verifiedCertLink} of completion to showcase on your resume"
values={{ values={{ verifiedCertLink }}
verifiedCertLink: (
<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href={`${getConfig().MARKETING_SITE_BASE_URL}/verified-certificate`}>verified certificate</a>
),
}}
/> />
</li> </li>
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.unlock-graded" id="learning.outline.widgets.upgradeCard.unlockGraded"
defaultMessage="Unlock your access to all course activities, including {gradedAssignments}" defaultMessage="Unlock your access to all course activities, including {gradedAssignments}"
values={{ values={{ gradedAssignments }}
gradedAssignments: (
<span className="font-weight-bold">graded assignments</span>
),
}}
/> />
</li> </li>
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.fullAccess" id="learning.outline.widgets.upgradeCard.fullAccess"
defaultMessage="{fullAccess} to course content and materials, even after the course ends" defaultMessage="{fullAccess} to course content and materials, even after the course ends"
values={{ values={{ fullAccess }}
fullAccess: (
<span className="font-weight-bold">Full access</span>
),
}}
/> />
</li> </li>
<li> <li>
<span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li upgrade-card-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.nonProfitMission" id="learning.outline.widgets.upgradeCard.nonProfitMission"
defaultMessage="Support our {nonProfitMission} at edX" defaultMessage="Support our {nonProfitMission} at edX"
values={{ values={{ nonProfitMission }}
nonProfitMission: (
<span className="font-weight-bold">non-profit mission</span>
),
}}
/> />
</li> </li>
</ul> </ul>
@@ -96,33 +121,51 @@ function UpsellFBEFarAwayCardContent() {
} }
function UpsellFBESoonCardContent({ accessExpirationDate, timezoneFormatArgs }) { function UpsellFBESoonCardContent({ accessExpirationDate, timezoneFormatArgs }) {
const includingAnyProgress = (
<span className="font-weight-bold">
<FormattedMessage
id="learning.upgradeCard.expirationAccessLoss.progress"
defaultMessage="including any progress"
/>
</span>
);
const date = (
<FormattedDate
key="accessDate"
day="numeric"
month="long"
value={new Date(accessExpirationDate)}
{...timezoneFormatArgs}
/>
);
const benefitsOfUpgrading = (
<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href="https://support.edx.org/hc/en-us/articles/360013426573-What-are-the-differences-between-audit-free-and-verified-paid-courses-">
<FormattedMessage
id="learning.outline.widgets.upgradeCard.expirationVerifiedCert.benefits"
defaultMessage="benefits of upgrading"
/>
</a>
);
return ( return (
<div className="upgrade-card-text"> <div className="upgrade-card-text">
<p> <p>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expirationAccessLoss" id="learning.outline.widgets.upgradeCard.expirationAccessLoss"
defaultMessage="You will lose all access to this course, {includingAnyProgress}, on {date}." defaultMessage="You will lose all access to this course, {includingAnyProgress}, on {date}."
values={{ values={{
includingAnyProgress: (<span className="font-weight-bold">including any progress</span>), includingAnyProgress,
date: ( date,
<FormattedDate
key="accessDate"
day="numeric"
month="long"
value={new Date(accessExpirationDate)}
{...timezoneFormatArgs}
/>
),
}} }}
/> />
</p> </p>
<p> <p>
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expirationVerifiedCert" id="learning.outline.widgets.upgradeCard.expirationVerifiedCert"
defaultMessage="Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}." defaultMessage="Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}."
values={{ values={{ benefitsOfUpgrading }}
benefitsOfUpgrading: (<a className="inline-link-underline font-weight-bold" rel="noopener noreferrer" target="_blank" href="https://support.edx.org/hc/en-us/articles/360013426573-What-are-the-differences-between-audit-free-and-verified-paid-courses-">benefits of upgrading</a>),
}}
/> />
</p> </p>
</div> </div>
@@ -146,7 +189,7 @@ function ExpirationCountdown({ hoursToExpiration }) {
if (hoursToExpiration >= 24) { if (hoursToExpiration >= 24) {
expirationText = ( expirationText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expiration.days" id="learning.outline.widgets.upgradeCard.expirationDays"
defaultMessage={`{dayCount, number} {dayCount, plural, defaultMessage={`{dayCount, number} {dayCount, plural,
one {day} one {day}
other {days}} left`} other {days}} left`}
@@ -158,7 +201,7 @@ function ExpirationCountdown({ hoursToExpiration }) {
} else if (hoursToExpiration >= 1) { } else if (hoursToExpiration >= 1) {
expirationText = ( expirationText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expiration.hours" id="learning.outline.widgets.upgradeCard.expirationHours"
defaultMessage={`{hourCount, number} {hourCount, plural, defaultMessage={`{hourCount, number} {hourCount, plural,
one {hour} one {hour}
other {hours}} left`} other {hours}} left`}
@@ -170,7 +213,7 @@ function ExpirationCountdown({ hoursToExpiration }) {
} else { } else {
expirationText = ( expirationText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expiration.minutes" id="learning.outline.widgets.upgradeCard.expirationMinutes"
defaultMessage="Less than 1 hour left" defaultMessage="Less than 1 hour left"
/> />
); );
@@ -186,7 +229,7 @@ function AccessExpirationDateBanner({ accessExpirationDate, timezoneFormatArgs }
return ( return (
<div className="upsell-warning-light"> <div className="upsell-warning-light">
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.expirationr" id="learning.outline.widgets.upgradeCard.expiration"
defaultMessage="Course access will expire {date}" defaultMessage="Course access will expire {date}"
values={{ values={{
date: ( date: (
@@ -286,7 +329,7 @@ function UpgradeCard({
offerCode = ( offerCode = (
<div className="text-center discount-info"> <div className="text-center discount-info">
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.code" id="learning.outline.widgets.upgradeCard.code"
defaultMessage="Use code {code} at checkout" defaultMessage="Use code {code} at checkout"
values={{ values={{
code: (<span className="font-weight-bold">{offer.code}</span>), code: (<span className="font-weight-bold">{offer.code}</span>),
@@ -301,7 +344,7 @@ function UpgradeCard({
const hoursToDiscountExpiration = Math.floor((new Date(offer.expirationDate) - correctedTime) / 1000 / 60 / 60); const hoursToDiscountExpiration = Math.floor((new Date(offer.expirationDate) - correctedTime) / 1000 / 60 / 60);
upgradeCardHeaderText = ( upgradeCardHeaderText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.firstTimeLearnerDiscount" id="learning.outline.widgets.upgradeCard.firstTimeLearnerDiscount"
defaultMessage="{percentage}% First-Time Learner Discount" defaultMessage="{percentage}% First-Time Learner Discount"
values={{ values={{
percentage: (offer.percentage), percentage: (offer.percentage),
@@ -312,7 +355,7 @@ function UpgradeCard({
} else { } else {
upgradeCardHeaderText = ( upgradeCardHeaderText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.accessExpiration" id="learning.outline.widgets.upgradeCard.accessExpiration"
defaultMessage="Upgrade your course today" defaultMessage="Upgrade your course today"
/> />
); );
@@ -327,7 +370,7 @@ function UpgradeCard({
} else { // more urgent messaging if there's less than 7 days left to access expiration } else { // more urgent messaging if there's less than 7 days left to access expiration
upgradeCardHeaderText = ( upgradeCardHeaderText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.accessExpirationUrgent" id="learning.outline.widgets.upgradeCard.accessExpirationUrgent"
defaultMessage="Course Access Expiration" defaultMessage="Course Access Expiration"
/> />
); );
@@ -342,7 +385,7 @@ function UpgradeCard({
} else { // FBE is turned off } else { // FBE is turned off
upgradeCardHeaderText = ( upgradeCardHeaderText = (
<FormattedMessage <FormattedMessage
id="learning.outline.alert.upgradecard.pursueAverifiedCertificate" id="learning.outline.widgets.upgradeCard.pursueAverifiedCertificate"
defaultMessage="Pursue a verified certificate" defaultMessage="Pursue a verified certificate"
/> />
); );

View File

@@ -4,7 +4,6 @@
.upgrade-card-header{ .upgrade-card-header{
margin: 1.25rem; margin: 1.25rem;
} }
.upsell-warning{ .upsell-warning{

View File

@@ -134,8 +134,8 @@ describe('Upgrade Card', () => {
expirationDate: discountExpirationDate.toString(), expirationDate: discountExpirationDate.toString(),
percentage: 15, percentage: 15,
code: 'Welcome15', code: 'Welcome15',
discountedPrice: '126.65', discountedPrice: '$126.65',
originalPrice: '149', originalPrice: '$149',
upgradeUrl: 'www.exampleUpgradeUrl.com', upgradeUrl: 'www.exampleUpgradeUrl.com',
}, },
}); });
@@ -163,8 +163,8 @@ describe('Upgrade Card', () => {
expirationDate: discountExpirationDate.toString(), expirationDate: discountExpirationDate.toString(),
percentage: 15, percentage: 15,
code: 'Welcome15', code: 'Welcome15',
discountedPrice: '126.65', discountedPrice: '$126.65',
originalPrice: '149', originalPrice: '$149',
upgradeUrl: 'www.exampleUpgradeUrl.com', upgradeUrl: 'www.exampleUpgradeUrl.com',
}, },
}); });
@@ -192,8 +192,8 @@ describe('Upgrade Card', () => {
expirationDate: discountExpirationDate.toString(), expirationDate: discountExpirationDate.toString(),
percentage: 15, percentage: 15,
code: 'Welcome15', code: 'Welcome15',
discountedPrice: '126.65', discountedPrice: '$126.65',
originalPrice: '149', originalPrice: '$149',
upgradeUrl: 'www.exampleUpgradeUrl.com', upgradeUrl: 'www.exampleUpgradeUrl.com',
}, },
}); });
@@ -221,8 +221,8 @@ describe('Upgrade Card', () => {
expirationDate: discountExpirationDate.toString(), expirationDate: discountExpirationDate.toString(),
percentage: 15, percentage: 15,
code: 'Welcome15', code: 'Welcome15',
discountedPrice: '126.65', discountedPrice: '$126.65',
originalPrice: '149', originalPrice: '$149',
upgradeUrl: 'www.exampleUpgradeUrl.com', upgradeUrl: 'www.exampleUpgradeUrl.com',
}, },
}); });

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { Factory } from 'rosie'; import { Factory } from 'rosie';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
@@ -52,6 +53,42 @@ describe('Progress Tab', () => {
logUnhandledRequests(axiosMock); logUnhandledRequests(axiosMock);
}); });
describe('Related links', () => {
beforeEach(() => {
sendTrackEvent.mockClear();
});
it('sends event on click of dates tab link', async () => {
await fetchAndRender();
const datesTabLink = screen.getByRole('link', { name: 'Dates' });
fireEvent.click(datesTabLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.related_links.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
link_clicked: 'dates',
});
});
it('sends event on click of outline tab link', async () => {
await fetchAndRender();
const outlineTabLink = screen.getAllByRole('link', { name: 'Course Outline' });
fireEvent.click(outlineTabLink[1]); // outlineTabLink[0] corresponds to the link in the DetailedGrades component
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.related_links.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
link_clicked: 'course_outline',
});
});
});
describe('Course Grade', () => { describe('Course Grade', () => {
it('renders Course Grade', async () => { it('renders Course Grade', async () => {
await fetchAndRender(); await fetchAndRender();
@@ -59,26 +96,45 @@ describe('Progress Tab', () => {
expect(screen.getByText('This represents your weighted grade against the grade needed to pass this course.')).toBeInTheDocument(); expect(screen.getByText('This represents your weighted grade against the grade needed to pass this course.')).toBeInTheDocument();
}); });
it('renders correct copy for non-passing', async () => { it('renders correct copy in CourseGradeFooter for non-passing', async () => {
setTabData({
course_grade: {
is_passing: false,
letter_grade: null,
percent: 0.5,
},
section_scores: [
{
display_name: 'First section',
subsections: [
{
assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 2,
percent_graded: 0.0,
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection',
},
],
},
],
});
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByRole('button', { name: 'Grade range tooltip' })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Grade range tooltip' })).not.toBeInTheDocument();
expect(screen.getByText('A weighted grade of 75% is required to pass in this course')).toBeInTheDocument(); expect(screen.getByText('A weighted grade of 75% is required to pass in this course')).toBeInTheDocument();
}); });
it('renders correct copy for passing with pass/fail grade range', async () => { it('renders correct copy in CourseGradeFooter for passing with pass/fail grade range', async () => {
setTabData({
course_grade: {
is_passing: true,
letter_grade: 'Pass',
percent: 0.9,
},
});
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByRole('button', { name: 'Grade range tooltip' })).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Grade range tooltip' })).not.toBeInTheDocument();
expect(screen.getByText('Youre currently passing this course')).toBeInTheDocument(); expect(screen.getByText('Youre currently passing this course')).toBeInTheDocument();
}); });
it('renders correct copy and tooltip for non-passing with letter grade range', async () => { it('renders correct copy and tooltip in CourseGradeFooter for non-passing with letter grade range', async () => {
setTabData({ setTabData({
course_grade: { course_grade: {
is_passing: false, is_passing: false,
@@ -105,13 +161,32 @@ describe('Progress Tab', () => {
expect(screen.getByText('A weighted grade of 80% is required to pass in this course')).toBeInTheDocument(); expect(screen.getByText('A weighted grade of 80% is required to pass in this course')).toBeInTheDocument();
}); });
it('renders correct copy and tooltip for passing with letter grade range', async () => { it('renders correct copy and tooltip in CourseGradeFooter for passing with letter grade range', async () => {
setTabData({ setTabData({
course_grade: { course_grade: {
is_passing: true, is_passing: true,
letter_grade: 'B', letter_grade: 'B',
percent: 0.85, percent: 0.8,
}, },
section_scores: [
{
display_name: 'First section',
subsections: [
{
assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection',
has_graded_assignment: true,
num_points_earned: 8,
num_points_possible: 10,
percent_graded: 1.0,
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection',
},
],
},
],
grading_policy: { grading_policy: {
assignment_policies: [ assignment_policies: [
{ {
@@ -132,7 +207,7 @@ describe('Progress Tab', () => {
expect(await screen.findByText('Youre currently passing this course with a grade of B (80-90%)')).toBeInTheDocument(); expect(await screen.findByText('Youre currently passing this course with a grade of B (80-90%)')).toBeInTheDocument();
}); });
it('renders tooltip for grade range', async () => { it('renders tooltip in CourseGradeFooter for grade range', async () => {
setTabData({ setTabData({
course_grade: { course_grade: {
percent: 0, percent: 0,
@@ -162,7 +237,61 @@ describe('Progress Tab', () => {
expect(screen.getByText('F: <80%')); expect(screen.getByText('F: <80%'));
}); });
it('renders locked feature preview when user has locked content', async () => { it('renders locked feature preview (CourseGradeHeader) with upgrade button when user has locked content', async () => {
setTabData({
completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 1,
},
verified_mode: {
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
currency_symbol: '$',
price: 149,
sku: 'ABCD1234',
upgrade_url: 'edx.org/upgrade',
},
});
await fetchAndRender();
expect(screen.getByText('locked feature')).toBeInTheDocument();
expect(screen.getByText('Unlock to view grades and work towards a certificate.')).toBeInTheDocument();
expect(screen.getAllByRole('link', 'Unlock now')).toHaveLength(3);
});
it('sends event on click of upgrade button in locked content header (CourseGradeHeader)', async () => {
sendTrackEvent.mockClear();
setTabData({
completion_summary: {
complete_count: 1,
incomplete_count: 1,
locked_count: 1,
},
verified_mode: {
access_expiration_date: '2050-01-01T12:00:00',
currency: 'USD',
currency_symbol: '$',
price: 149,
sku: 'ABCD1234',
upgrade_url: 'edx.org/upgrade',
},
});
await fetchAndRender();
expect(screen.getByText('locked feature')).toBeInTheDocument();
expect(screen.getByText('Unlock to view grades and work towards a certificate.')).toBeInTheDocument();
const upgradeButton = screen.getAllByRole('link', 'Unlock now')[0];
fireEvent.click(upgradeButton);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.grades_upgrade.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
});
});
it('renders locked feature preview with no upgrade button when user has locked content but cannot upgrade', async () => {
setTabData({ setTabData({
completion_summary: { completion_summary: {
complete_count: 1, complete_count: 1,
@@ -172,11 +301,61 @@ describe('Progress Tab', () => {
}); });
await fetchAndRender(); await fetchAndRender();
expect(screen.getByText('locked feature')).toBeInTheDocument(); expect(screen.getByText('locked feature')).toBeInTheDocument();
expect(screen.getByText('The deadline to upgrade in this course has passed.')).toBeInTheDocument();
}); });
it('does not render locked feature preview when user does not have locked content', async () => { it('does not render locked feature preview when user does not have locked content', async () => {
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByText('locked feature')).not.toBeInTheDocument(); expect(screen.queryByText('locked feature')).not.toBeInTheDocument();
}); });
it('renders correct current grade tooltip when showGrades is false', async () => {
// The learner has a 50% on the first assignment and a 100% on the second, making their grade a 75%
// The second assignment has showGrades set to false, so the grade reflected to the learner should be 50%.
setTabData({
section_scores: [
{
display_name: 'First section',
subsections: [
{
assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 2,
percent_graded: 1.0,
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection',
},
],
},
{
display_name: 'Second section',
subsections: [
{
assignment_type: 'Homework',
display_name: 'Second subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 1,
percent_graded: 1.0,
show_correctness: 'always',
show_grades: false,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection',
},
],
},
],
});
await fetchAndRender();
expect(screen.getByTestId('currentGradeTooltipContent').innerHTML).toEqual('50%');
// Although the learner's true grade is passing, we should expect this to reflect the grade that's
// visible to them, which is non-passing
expect(screen.getByText('A weighted grade of 75% is required to pass in this course')).toBeInTheDocument();
});
}); });
describe('Grade Summary', () => { describe('Grade Summary', () => {
@@ -189,11 +368,185 @@ describe('Progress Tab', () => {
setTabData({ setTabData({
grading_policy: { grading_policy: {
assignment_policies: [], assignment_policies: [],
grade_range: {
pass: 0.75,
},
}, },
section_scores: [],
}); });
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByText('Grade summary')).not.toBeInTheDocument(); expect(screen.queryByText('Grade summary')).not.toBeInTheDocument();
}); });
it('calculates grades correctly when number of droppable assignments equals total number of assignments', async () => {
setTabData({
grading_policy: {
assignment_policies: [
{
num_droppable: 2,
num_total: 2,
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
pass: 0.75,
},
},
});
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {footnote - optional} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 1 100% 0% 0%' })).toBeInTheDocument();
});
it('calculates grades correctly when number of droppable assignments is less than total number of assignments', async () => {
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {footnote - optional} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 1 100% 100% 100%' })).toBeInTheDocument();
});
it('calculates grades correctly when number of droppable assignments is zero', async () => {
setTabData({
grading_policy: {
assignment_policies: [
{
num_droppable: 0,
num_total: 2,
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
pass: 0.75,
},
},
});
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 100% 50% 50%' })).toBeInTheDocument();
});
it('calculates grades correctly when number of total assignments is less than the number of assignments created', async () => {
setTabData({
grading_policy: {
assignment_policies: [
{
num_droppable: 1,
num_total: 1, // two assignments created in the factory, but 1 is expected per Studio settings
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
pass: 0.75,
},
},
});
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {footnote - optional} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 1 100% 100% 100%' })).toBeInTheDocument();
});
it('calculates grades correctly when number of total assignments is greater than the number of assignments created', async () => {
setTabData({
grading_policy: {
assignment_policies: [
{
num_droppable: 0,
num_total: 5, // two assignments created in the factory, but 5 are expected per Studio settings
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
pass: 0.75,
},
},
});
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 100% 20% 20%' })).toBeInTheDocument();
});
it('calculates weighted grades correctly', async () => {
setTabData({
grading_policy: {
assignment_policies: [
{
num_droppable: 1,
num_total: 2,
short_label: 'HW',
type: 'Homework',
weight: 0.5,
},
{
num_droppable: 0,
num_total: 1,
short_label: 'Ex',
type: 'Exam',
weight: 0.5,
},
],
grade_range: {
pass: 0.75,
},
},
});
await fetchAndRender();
expect(screen.getByText('Grade summary')).toBeInTheDocument();
// The row is comprised of "{Assignment type} {footnote - optional} {weight} {grade} {weighted grade}"
expect(screen.getByRole('row', { name: 'Homework 1 50% 100% 50%' })).toBeInTheDocument();
expect(screen.getByRole('row', { name: 'Exam 50% 0% 0%' })).toBeInTheDocument();
});
it('renders correct total weighted grade when showGrades is false', async () => {
// The learner has a 50% on the first assignment and a 100% on the second, making their grade a 75%
// The second assignment has showGrades set to false, so the grade reflected to the learner should be 50%.
setTabData({
section_scores: [
{
display_name: 'First section',
subsections: [
{
assignment_type: 'Homework',
block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
display_name: 'First subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 2,
percent_graded: 1.0,
show_correctness: 'always',
show_grades: true,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/first_subsection',
},
],
},
{
display_name: 'Second section',
subsections: [
{
assignment_type: 'Homework',
display_name: 'Second subsection',
has_graded_assignment: true,
num_points_earned: 1,
num_points_possible: 1,
percent_graded: 1.0,
show_correctness: 'always',
show_grades: false,
url: 'http://learning.edx.org/course/course-v1:edX+Test+run/second_subsection',
},
],
},
],
});
await fetchAndRender();
expect(screen.getByTestId('gradeSummaryFooterTotalWeightedGrade').innerHTML).toEqual('50%');
});
}); });
describe('Detailed Grades', () => { describe('Detailed Grades', () => {
@@ -205,6 +558,39 @@ describe('Progress Tab', () => {
expect(screen.getByRole('link', { name: 'Second subsection' })); expect(screen.getByRole('link', { name: 'Second subsection' }));
}); });
it('sends event on click of subsection link', async () => {
sendTrackEvent.mockClear();
await fetchAndRender();
expect(screen.getByText('Detailed grades')).toBeInTheDocument();
const subsectionLink = screen.getByRole('link', { name: 'First subsection' });
fireEvent.click(subsectionLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.detailed_grades_assignment.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
assignment_block_key: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@12345',
});
});
it('sends event on click of course outline link', async () => {
sendTrackEvent.mockClear();
await fetchAndRender();
expect(screen.getByText('Detailed grades')).toBeInTheDocument();
const outlineLink = screen.getAllByRole('link', { name: 'Course Outline' })[0];
fireEvent.click(outlineLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.detailed_grades.course_outline_link.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
});
});
it('render message when section scores are not populated', async () => { it('render message when section scores are not populated', async () => {
setTabData({ setTabData({
section_scores: [], section_scores: [],
@@ -238,6 +624,7 @@ describe('Progress Tab', () => {
describe('enrolled user', () => { describe('enrolled user', () => {
beforeEach(async () => { beforeEach(async () => {
setMetadata({ is_enrolled: true }); setMetadata({ is_enrolled: true });
sendTrackEvent.mockClear();
}); });
it('Displays text for nonPassing case when learner does not have a passing grade', async () => { it('Displays text for nonPassing case when learner does not have a passing grade', async () => {
@@ -245,6 +632,20 @@ describe('Progress Tab', () => {
expect(screen.getByText('In order to qualify for a certificate, you must have a passing grade.')).toBeInTheDocument(); expect(screen.getByText('In order to qualify for a certificate, you must have a passing grade.')).toBeInTheDocument();
}); });
it('sends event when visiting progress tab when learner is not passing', async () => {
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'not_passing',
certificate_status_variant: 'not_passing',
});
});
it('Displays text for inProgress case when more content is scheduled and the learner does not have a passing grade', async () => { it('Displays text for inProgress case when more content is scheduled and the learner does not have a passing grade', async () => {
setTabData({ setTabData({
has_scheduled_content: true, has_scheduled_content: true,
@@ -253,6 +654,23 @@ describe('Progress Tab', () => {
expect(screen.getByText('It looks like there is more content in this course that will be released in the future. Look out for email updates or check back on your course for when this content will be available.')).toBeInTheDocument(); expect(screen.getByText('It looks like there is more content in this course that will be released in the future. Look out for email updates or check back on your course for when this content will be available.')).toBeInTheDocument();
}); });
it('sends event when visiting progress tab when user has scheduled content', async () => {
setTabData({
has_scheduled_content: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'not_passing',
certificate_status_variant: 'has_scheduled_content',
});
});
it('Displays request certificate link', async () => { it('Displays request certificate link', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'requesting' }, certificate_data: { cert_status: 'requesting' },
@@ -262,6 +680,34 @@ describe('Progress Tab', () => {
expect(screen.getByRole('button', { name: 'Request certificate' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Request certificate' })).toBeInTheDocument();
}); });
it('sends events on view of progress tab and on click of request certificate link', async () => {
setTabData({
certificate_data: { cert_status: 'requesting' },
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'requesting',
});
const requestCertificateLink = screen.getByRole('button', { name: 'Request certificate' });
fireEvent.click(requestCertificateLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
certificate_status_variant: 'requesting',
});
});
it('Displays verify identity link', async () => { it('Displays verify identity link', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'unverified' }, certificate_data: { cert_status: 'unverified' },
@@ -272,6 +718,35 @@ describe('Progress Tab', () => {
expect(screen.getByRole('link', { name: 'Verify ID' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Verify ID' })).toBeInTheDocument();
}); });
it('sends events on view of progress tab and on click of ID verification link', async () => {
setTabData({
certificate_data: { cert_status: 'unverified' },
user_has_passing_grade: true,
verification_data: { link: 'test' },
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'unverified',
});
const idVerificationLink = screen.getByRole('link', { name: 'Verify ID' });
fireEvent.click(idVerificationLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
certificate_status_variant: 'unverified',
});
});
it('Displays verification pending message', async () => { it('Displays verification pending message', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'unverified' }, certificate_data: { cert_status: 'unverified' },
@@ -283,6 +758,25 @@ describe('Progress Tab', () => {
expect(screen.queryByRole('link', { name: 'Verify ID' })).not.toBeInTheDocument(); expect(screen.queryByRole('link', { name: 'Verify ID' })).not.toBeInTheDocument();
}); });
it('sends event when visiting progress tab with ID verification pending message', async () => {
setTabData({
certificate_data: { cert_status: 'unverified' },
verification_data: { status: 'pending' },
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'unverified',
});
});
it('Displays download link', async () => { it('Displays download link', async () => {
setTabData({ setTabData({
certificate_data: { certificate_data: {
@@ -295,6 +789,37 @@ describe('Progress Tab', () => {
expect(screen.getByRole('link', { name: 'Download my certificate' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Download my certificate' })).toBeInTheDocument();
}); });
it('sends events on view of progress tab and on click of downloadable certificate link', async () => {
setTabData({
certificate_data: {
cert_status: 'downloadable',
download_url: 'fake.download.url',
},
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'earned_downloadable',
});
const downloadCertificateLink = screen.getByRole('link', { name: 'Download my certificate' });
fireEvent.click(downloadCertificateLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
certificate_status_variant: 'earned_downloadable',
});
});
it('Displays webview link', async () => { it('Displays webview link', async () => {
setTabData({ setTabData({
certificate_data: { certificate_data: {
@@ -307,6 +832,37 @@ describe('Progress Tab', () => {
expect(screen.getByRole('link', { name: 'View my certificate' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'View my certificate' })).toBeInTheDocument();
}); });
it('sends events on view of progress tab and on click of view certificate link', async () => {
setTabData({
certificate_data: {
cert_status: 'downloadable',
cert_web_view_url: '/certificates/cooluuidgoeshere',
},
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'earned_viewable',
});
const viewCertificateLink = screen.getByRole('link', { name: 'View my certificate' });
fireEvent.click(viewCertificateLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
certificate_status_variant: 'earned_viewable',
});
});
it('Displays certificate is earned but unavailable message', async () => { it('Displays certificate is earned but unavailable message', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'earned_but_not_available' }, certificate_data: { cert_status: 'earned_but_not_available' },
@@ -316,6 +872,57 @@ describe('Progress Tab', () => {
expect(screen.queryByText('Certificate status')).toBeInTheDocument(); expect(screen.queryByText('Certificate status')).toBeInTheDocument();
}); });
it('sends event when visiting the progress tab when cert is earned but unavailable', async () => {
setTabData({
certificate_data: { cert_status: 'earned_but_not_available' },
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'earned_but_not_available',
});
});
it('sends event with correct grade variant for passing with letter grades', async () => {
setTabData({
certificate_data: { cert_status: 'earned_but_not_available' },
grading_policy: {
assignment_policies: [
{
num_droppable: 1,
num_total: 2,
short_label: 'HW',
type: 'Homework',
weight: 1,
},
],
grade_range: {
A: 0.9,
B: 0.8,
},
},
user_has_passing_grade: true,
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing_grades',
certificate_status_variant: 'earned_but_not_available',
});
});
it('Displays upgrade link when available', async () => { it('Displays upgrade link when available', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'audit_passing' }, certificate_data: { cert_status: 'audit_passing' },
@@ -330,6 +937,36 @@ describe('Progress Tab', () => {
expect(screen.getByRole('link', { name: 'Upgrade now' })).toBeInTheDocument(); expect(screen.getByRole('link', { name: 'Upgrade now' })).toBeInTheDocument();
}); });
it('sends events on view of progress tab and when audit learner clicks upgrade link', async () => {
setTabData({
certificate_data: { cert_status: 'audit_passing' },
verified_mode: {
upgrade_url: 'http://localhost:18130/basket/add/?sku=8CF08E5',
},
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'not_passing',
certificate_status_variant: 'audit_passing',
});
const upgradeLink = screen.getByRole('link', { name: 'Upgrade now' });
fireEvent.click(upgradeLink);
expect(sendTrackEvent).toHaveBeenCalledTimes(2);
expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
certificate_status_variant: 'audit_passing',
});
});
it('Displays nothing if audit only', async () => { it('Displays nothing if audit only', async () => {
setTabData({ setTabData({
certificate_data: { cert_status: 'audit_passing' }, certificate_data: { cert_status: 'audit_passing' },
@@ -341,6 +978,23 @@ describe('Progress Tab', () => {
expect(screen.queryByRole('link', { name: 'Upgrade now' })).not.toBeInTheDocument(); expect(screen.queryByRole('link', { name: 'Upgrade now' })).not.toBeInTheDocument();
}); });
it('sends event when visiting the progress tab even when audit user cannot upgrade (i.e. certificate component does not render)', async () => {
setTabData({
certificate_data: { cert_status: 'audit_passing' },
});
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'not_passing',
certificate_status_variant: 'audit_passing_missed_upgrade_deadline',
});
});
it('Does not display the certificate component if it does not match any statuses', async () => { it('Does not display the certificate component if it does not match any statuses', async () => {
setTabData({ setTabData({
certificate_data: { certificate_data: {
@@ -352,6 +1006,27 @@ describe('Progress Tab', () => {
await fetchAndRender(); await fetchAndRender();
expect(screen.queryByTestId('certificate-status-component')).not.toBeInTheDocument(); expect(screen.queryByTestId('certificate-status-component')).not.toBeInTheDocument();
}); });
it('sends event when visiting progress tab, although no certificate statuses match', async () => {
setTabData({
certificate_data: {
cert_status: 'bogus_status',
},
user_has_passing_grade: true,
});
setMetadata({ is_enrolled: true });
await fetchAndRender();
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.course_progress.visited', {
org_key: 'edX',
courserun_key: courseId,
is_staff: false,
track_variant: 'audit',
grade_variant: 'passing',
certificate_status_variant: 'certificate_status_disabled',
});
});
}); });
it('Does not display the certificate component if the user is not enrolled', async () => { it('Does not display the certificate component if the user is not enrolled', async () => {

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { import {
FormattedDate, FormattedMessage, injectIntl, intlShape, FormattedDate, FormattedMessage, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n'; } from '@edx/frontend-platform/i18n';
@@ -19,12 +21,20 @@ function CertificateStatus({ intl }) {
const { const {
isEnrolled, isEnrolled,
org,
} = useModel('courseHomeMeta', courseId); } = useModel('courseHomeMeta', courseId);
const { const {
certificateData, certificateData,
end,
enrollmentMode,
gradingPolicy: {
gradeRange,
},
hasScheduledContent, hasScheduledContent,
userHasPassingGrade, userHasPassingGrade,
verificationData,
verifiedMode,
} = useModel('progress', courseId); } = useModel('progress', courseId);
const mode = getCourseExitMode( const mode = getCourseExitMode(
@@ -34,39 +44,48 @@ function CertificateStatus({ intl }) {
userHasPassingGrade, userHasPassingGrade,
); );
const dispatch = useDispatch(); const dispatch = useDispatch();
const { administrator } = getAuthenticatedUser();
const { let certStatus;
end, let certWebViewUrl;
verificationData, let downloadUrl;
certificateData: {
certStatus, if (certificateData) {
certWebViewUrl, certStatus = certificateData.certStatus;
downloadUrl, certWebViewUrl = certificateData.certWebViewUrl;
}, downloadUrl = certificateData.downloadUrl;
verifiedMode, }
} = useModel('progress', courseId);
let certCase; let certCase;
let certEventName = certStatus;
let body; let body;
let buttonAction; let buttonAction;
let buttonLocation; let buttonLocation;
let buttonText; let buttonText;
let endDate; let endDate;
let gradeEventName = 'not_passing';
if (userHasPassingGrade) {
gradeEventName = Object.entries(gradeRange).length > 1 ? 'passing_grades' : 'passing';
}
const dashboardLink = <DashboardLink />; const dashboardLink = <DashboardLink />;
const idVerificationSupportLink = <IdVerificationSupportLink />; const idVerificationSupportLink = <IdVerificationSupportLink />;
const profileLink = <ProfileLink />; const profileLink = <ProfileLink />;
if (mode === COURSE_EXIT_MODES.nonPassing) { if (mode === COURSE_EXIT_MODES.disabled) {
certEventName = 'certificate_status_disabled';
} else if (mode === COURSE_EXIT_MODES.nonPassing) {
certCase = 'notPassing'; certCase = 'notPassing';
certEventName = 'not_passing';
body = intl.formatMessage(messages[`${certCase}Body`]); body = intl.formatMessage(messages[`${certCase}Body`]);
} else if (mode === COURSE_EXIT_MODES.inProgress) { } else if (mode === COURSE_EXIT_MODES.inProgress) {
certCase = 'inProgress'; certCase = 'inProgress';
certEventName = 'has_scheduled_content';
body = intl.formatMessage(messages[`${certCase}Body`]); body = intl.formatMessage(messages[`${certCase}Body`]);
} else if (mode === COURSE_EXIT_MODES.celebration) { } else if (mode === COURSE_EXIT_MODES.celebration) {
switch (certStatus) { switch (certStatus) {
case 'requesting': case 'requesting':
// Requestable
certCase = 'requestable'; certCase = 'requestable';
buttonAction = () => { dispatch(requestCert(courseId)); }; buttonAction = () => { dispatch(requestCert(courseId)); };
body = intl.formatMessage(messages[`${certCase}Body`]); body = intl.formatMessage(messages[`${certCase}Body`]);
@@ -105,9 +124,11 @@ function CertificateStatus({ intl }) {
); );
if (certWebViewUrl) { if (certWebViewUrl) {
certEventName = 'earned_viewable';
buttonLocation = `${getConfig().LMS_BASE_URL}${certWebViewUrl}`; buttonLocation = `${getConfig().LMS_BASE_URL}${certWebViewUrl}`;
buttonText = intl.formatMessage(messages.viewableButton); buttonText = intl.formatMessage(messages.viewableButton);
} else if (downloadUrl) { } else if (downloadUrl) {
certEventName = 'earned_downloadable';
buttonLocation = downloadUrl; buttonLocation = downloadUrl;
buttonText = intl.formatMessage(messages.downloadableButton); buttonText = intl.formatMessage(messages.downloadableButton);
} }
@@ -133,22 +154,47 @@ function CertificateStatus({ intl }) {
body = intl.formatMessage(messages[`${certCase}Body`]); body = intl.formatMessage(messages[`${certCase}Body`]);
buttonLocation = verifiedMode.upgradeUrl; buttonLocation = verifiedMode.upgradeUrl;
buttonText = intl.formatMessage(messages[`${certCase}Button`]); buttonText = intl.formatMessage(messages[`${certCase}Button`]);
} else {
certCase = null; // Do not render the certificate component if the upgrade deadline has passed
certEventName = 'audit_passing_missed_upgrade_deadline';
} }
break; break;
// This code shouldn't be hit but coding defensively since switch expects a default statement // This code shouldn't be hit but coding defensively since switch expects a default statement
default: default:
certCase = null; certCase = null;
certEventName = 'no_certificate_status';
break; break;
} }
} }
// Log visit to progress tab
useEffect(() => {
sendTrackEvent('edx.ui.lms.course_progress.visited', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
track_variant: enrollmentMode,
grade_variant: gradeEventName,
certificate_status_variant: certEventName,
});
}, []);
if (!certCase) { if (!certCase) {
return null; return null;
} }
const header = intl.formatMessage(messages[`${certCase}Header`]); const header = intl.formatMessage(messages[`${certCase}Header`]);
const logCertificateStatusButtonClicked = () => {
sendTrackEvent('edx.ui.lms.course_progress.certificate_status.clicked', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
certificate_status_variant: certEventName,
});
};
return ( return (
<section data-testid="certificate-status-component" className="text-dark-700 mb-4"> <section data-testid="certificate-status-component" className="text-dark-700 mb-4">
<Card className="bg-light-200 shadow-sm border-0"> <Card className="bg-light-200 shadow-sm border-0">
@@ -160,7 +206,17 @@ function CertificateStatus({ intl }) {
{body} {body}
</Card.Text> </Card.Text>
{buttonText && (buttonLocation || buttonAction) && ( {buttonText && (buttonLocation || buttonAction) && (
<Button variant="outline-brand" onClick={buttonAction} href={buttonLocation} block>{buttonText}</Button> <Button
variant="outline-brand"
onClick={() => {
logCertificateStatusButtonClicked(certStatus);
if (buttonAction) { buttonAction(); }
}}
href={buttonLocation}
block
>
{buttonText}
</Button>
)} )}
</Card.Body> </Card.Body>
</Card> </Card>

View File

@@ -22,8 +22,8 @@ function CompletionDonutChart({ intl }) {
} = useModel('progress', courseId); } = useModel('progress', courseId);
const numTotalUnits = completeCount + incompleteCount + lockedCount; const numTotalUnits = completeCount + incompleteCount + lockedCount;
const completePercentage = Number(((completeCount / numTotalUnits) * 100).toFixed(0)); const completePercentage = completeCount ? Number(((completeCount / numTotalUnits) * 100).toFixed(0)) : 0;
const lockedPercentage = Number(((lockedCount / numTotalUnits) * 100).toFixed(0)); const lockedPercentage = lockedCount ? Number(((lockedCount / numTotalUnits) * 100).toFixed(0)) : 0;
const incompletePercentage = 100 - completePercentage - lockedPercentage; const incompletePercentage = 100 - completePercentage - lockedPercentage;
return ( return (

View File

@@ -24,14 +24,7 @@ function CourseGrade({ intl }) {
}, },
} = useModel('progress', courseId); } = useModel('progress', courseId);
let passingGrade; const passingGrade = Number((Math.min(...Object.values(gradeRange)) * 100).toFixed(0));
if (gradeRange.pass) {
passingGrade = gradeRange.pass * 100;
} else {
passingGrade = Object.entries(gradeRange).pop()[1] * 100;
}
passingGrade = Number(passingGrade.toFixed(0));
const isLocked = lockedCount > 0; const isLocked = lockedCount > 0;
const applyLockedOverlay = isLocked ? 'locked-overlay' : ''; const applyLockedOverlay = isLocked ? 'locked-overlay' : '';

View File

@@ -40,15 +40,17 @@ function CourseGradeFooter({ intl, passingGrade }) {
if (isPassing) { if (isPassing) {
if (hasLetterGrades) { if (hasLetterGrades) {
const letterGrades = Object.keys(gradeRange); const minGradeRangeCutoff = gradeRange[letterGrade] * 100;
const gradeIndex = letterGrades.indexOf(letterGrade); const possibleMaxGradeRangeValues = [...Object.values(gradeRange).filter(
const minGrade = gradeRange[letterGrade] * 100; (grade) => (grade * 100 > minGradeRangeCutoff),
const maxGrade = gradeIndex > 0 ? gradeRange[letterGrades[gradeIndex - 1]] * 100 : 100; )];
const maxGradeRangeCutoff = possibleMaxGradeRangeValues.length ? Math.min(...possibleMaxGradeRangeValues) * 100
: 100;
footerText = intl.formatMessage(messages.courseGradeFooterPassingWithGrade, { footerText = intl.formatMessage(messages.courseGradeFooterPassingWithGrade, {
letterGrade, letterGrade,
minGrade: minGrade.toFixed(0), minGrade: minGradeRangeCutoff.toFixed(0),
maxGrade: maxGrade.toFixed(0), maxGrade: maxGradeRangeCutoff.toFixed(0),
}); });
} else { } else {
footerText = intl.formatMessage(messages.courseGradeFooterGenericPassing); footerText = intl.formatMessage(messages.courseGradeFooterGenericPassing);

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Locked } from '@edx/paragon/icons'; import { Locked } from '@edx/paragon/icons';
import { Button, Icon } from '@edx/paragon'; import { Button, Icon } from '@edx/paragon';
@@ -12,12 +14,24 @@ function CourseGradeHeader({ intl }) {
const { const {
courseId, courseId,
} = useSelector(state => state.courseHome); } = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const { const {
verifiedMode, verifiedMode,
} = useModel('progress', courseId); } = useModel('progress', courseId);
const { administrator } = getAuthenticatedUser();
const logUpgradeButtonClick = () => {
sendTrackEvent('edx.ui.lms.course_progress.grades_upgrade.clicked', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
});
};
return ( return (
<div className="row w-100 m-0 p-4 rounded-top bg-primary-500 text-white"> <div className="row w-100 m-0 p-4 rounded-top bg-primary-500 text-white">
<div className="col-12 col-md-9 p-0"> <div className={`col-12 ${verifiedMode ? 'col-md-9' : ''} p-0`}>
<div className="row w-100 m-0 p-0"> <div className="row w-100 m-0 p-0">
<div className="col-1 p-0"> <div className="col-1 p-0">
<Icon src={Locked} /> <Icon src={Locked} />
@@ -31,17 +45,18 @@ function CourseGradeHeader({ intl }) {
</div> </div>
<div className="row w-100 m-0 p-0 justify-content-end"> <div className="row w-100 m-0 p-0 justify-content-end">
<div className="col-11 px-2 p-sm-0 small"> <div className="col-11 px-2 p-sm-0 small">
{intl.formatMessage(messages.courseGradePreviewBody)} {verifiedMode ? intl.formatMessage(messages.courseGradePreviewUnlockCertificateBody)
: intl.formatMessage(messages.courseGradePreviewUpgradeDeadlinePassedBody)}
</div> </div>
</div> </div>
</div> </div>
<div className="col-12 col-md-3 mt-3 mt-md-0 p-0 align-self-center text-right"> {verifiedMode && (
{verifiedMode && ( <div className="col-12 col-md-3 mt-3 mt-md-0 p-0 align-self-center text-right">
<Button variant="brand" size="sm" href={verifiedMode.upgradeUrl}> <Button variant="brand" size="sm" href={verifiedMode.upgradeUrl} onClick={logUpgradeButtonClick}>
{intl.formatMessage(messages.courseGradePreviewUpgradeButton)} {intl.formatMessage(messages.courseGradePreviewUpgradeButton)}
</Button> </Button>
)} </div>
</div> )}
</div> </div>
); );
} }

View File

@@ -17,11 +17,11 @@ function CurrentGradeTooltip({ intl, tooltipClassName }) {
const { const {
courseGrade: { courseGrade: {
isPassing, isPassing,
percent, visiblePercent,
}, },
} = useModel('progress', courseId); } = useModel('progress', courseId);
const currentGrade = percent * 100; const currentGrade = Number((visiblePercent * 100).toFixed(0));
return ( return (
<> <>
@@ -30,21 +30,21 @@ function CurrentGradeTooltip({ intl, tooltipClassName }) {
placement="top" placement="top"
overlay={( overlay={(
<Popover id={`${isPassing ? 'passing' : 'non-passing'}-grade-tooltip`} aria-hidden="true" className={tooltipClassName}> <Popover id={`${isPassing ? 'passing' : 'non-passing'}-grade-tooltip`} aria-hidden="true" className={tooltipClassName}>
<Popover.Content className={isPassing ? 'text-white' : 'text-dark-700'}> <Popover.Content data-testid="currentGradeTooltipContent" className={isPassing ? 'text-white' : 'text-dark-700'}>
{currentGrade.toFixed(0)}% {currentGrade.toFixed(0)}%
</Popover.Content> </Popover.Content>
</Popover> </Popover>
)} )}
> >
<g> <g>
<circle cx={`${currentGrade}%`} cy="50%" r="8.5" fill="transparent" /> <circle cx={`${Math.min(...[currentGrade, 100])}%`} cy="50%" r="8.5" fill="transparent" />
<rect className="grade-bar__divider" x={`${currentGrade}%`} style={{ transform: 'translateY(2.61em)' }} /> <rect className="grade-bar__divider" x={`${Math.min(...[currentGrade, 100])}%`} style={{ transform: 'translateY(2.61em)' }} />
</g> </g>
</OverlayTrigger> </OverlayTrigger>
<text <text
className="x-small" className="x-small"
textAnchor={currentGrade < 50 ? 'start' : 'end'} textAnchor={currentGrade < 50 ? 'start' : 'end'}
x={`${currentGrade}%`} x={`${Math.min(...[currentGrade, 100])}%`}
y="20px" y="20px"
style={{ transform: `translateX(${currentGrade < 50 ? '' : '-'}3.4em)` }} style={{ transform: `translateX(${currentGrade < 50 ? '' : '-'}3.4em)` }}
> >

View File

@@ -20,11 +20,11 @@ function GradeBar({ intl, passingGrade }) {
}, },
courseGrade: { courseGrade: {
isPassing, isPassing,
percent, visiblePercent,
}, },
} = useModel('progress', courseId); } = useModel('progress', courseId);
const currentGrade = percent * 100; const currentGrade = Number((visiblePercent * 100).toFixed(0));
const isLocked = lockedCount > 0; const isLocked = lockedCount > 0;
const lockedTooltipClassName = isLocked ? 'locked-overlay' : ''; const lockedTooltipClassName = isLocked ? 'locked-overlay' : '';

View File

@@ -24,7 +24,9 @@ function GradeRangeTooltip({ intl, iconButtonClassName, passingGrade }) {
const [showTooltip, setShowTooltip] = useState(false); const [showTooltip, setShowTooltip] = useState(false);
const gradeRangeEntries = Object.entries(gradeRange); const orderedGradeRange = Object.entries(gradeRange).sort((a, b) => (
gradeRange[b[0]] - gradeRange[a[0]]
));
return ( return (
<OverlayTrigger <OverlayTrigger
@@ -36,18 +38,18 @@ function GradeRangeTooltip({ intl, iconButtonClassName, passingGrade }) {
<Popover.Content className="px-3"> <Popover.Content className="px-3">
{intl.formatMessage(messages.courseGradeRangeTooltip)} {intl.formatMessage(messages.courseGradeRangeTooltip)}
<ul className="list-unstyled m-0"> <ul className="list-unstyled m-0">
{gradeRangeEntries.map((entry, index) => { {orderedGradeRange.map((range, index) => {
if (index === 0) { if (index === 0) {
return ( return (
<li key={entry[0]}> <li key={range[0]}>
{entry[0]}: {(entry[1] * 100).toFixed(0)}%-100% {range[0]}: {(range[1] * 100).toFixed(0)}%-100%
</li> </li>
); );
} }
const previousGrade = gradeRangeEntries[index - 1]; const previousGrade = orderedGradeRange[index - 1];
return ( return (
<li key={entry[0]}> <li key={range[0]}>
{entry[0]}: {(entry[1] * 100).toFixed(0)}%-{(previousGrade[1] * 100).toFixed(0)}% {range[0]}: {(range[1] * 100).toFixed(0)}%-{(previousGrade[1] * 100).toFixed(0)}%
</li> </li>
); );
})} })}

View File

@@ -1,8 +1,11 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import { useModel } from '../../../../generic/model-store'; import { useModel } from '../../../../generic/model-store';
import DetailedGradesTable from './DetailedGradesTable'; import DetailedGradesTable from './DetailedGradesTable';
@@ -10,24 +13,35 @@ import DetailedGradesTable from './DetailedGradesTable';
import messages from '../messages'; import messages from '../messages';
function DetailedGrades({ intl }) { function DetailedGrades({ intl }) {
const { administrator } = getAuthenticatedUser();
const { const {
courseId, courseId,
} = useSelector(state => state.courseHome); } = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const { const {
sectionScores, sectionScores,
} = useModel('progress', courseId); } = useModel('progress', courseId);
const hasSectionScores = sectionScores.length > 0; const hasSectionScores = sectionScores.length > 0;
const logOutlineLinkClick = () => {
sendTrackEvent('edx.ui.lms.course_progress.detailed_grades.course_outline_link.clicked', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
});
};
const outlineLink = ( const outlineLink = (
<Link <Hyperlink
className="text-dark-700" className="muted-link inline-link"
style={{ textDecoration: 'underline' }} destination={`${getConfig().LMS_BASE_URL}/courses/${courseId}/course`}
to={`/course/${courseId}/home`} onClick={logOutlineLinkClick}
> >
{intl.formatMessage(messages.courseOutline)} {intl.formatMessage(messages.courseOutline)}
</Link> </Hyperlink>
); );
return ( return (

View File

@@ -1,12 +1,31 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { DataTable } from '@edx/paragon'; import { DataTable } from '@edx/paragon';
import messages from '../messages'; import messages from '../messages';
import { useModel } from '../../../../generic/model-store';
function DetailedGradesTable({ intl, sectionScores }) { function DetailedGradesTable({ intl, sectionScores }) {
const {
courseId,
} = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const { administrator } = getAuthenticatedUser();
const logSubsectionClicked = (blockKey) => {
sendTrackEvent('edx.ui.lms.course_progress.detailed_grades_assignment.clicked', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
assignment_block_key: blockKey,
});
};
return ( return (
sectionScores.map((chapter) => { sectionScores.map((chapter) => {
const subsectionScores = chapter.subsections.filter( const subsectionScores = chapter.subsections.filter(
@@ -21,7 +40,17 @@ function DetailedGradesTable({ intl, sectionScores }) {
} }
const detailedGradesData = subsectionScores.map((subsection) => { const detailedGradesData = subsectionScores.map((subsection) => {
const title = <a href={subsection.url} className="text-dark-700 small">{subsection.displayName}</a>; const title = (
<a
href={subsection.url}
className="text-dark-700 small"
onClick={() => {
logSubsectionClicked(subsection.blockKey);
}}
>
{subsection.displayName}
</a>
);
return { return {
subsectionTitle: title, subsectionTitle: title,
score: `${subsection.numPointsEarned}/${subsection.numPointsPossible}`, score: `${subsection.numPointsEarned}/${subsection.numPointsPossible}`,

View File

@@ -24,7 +24,7 @@ AssignmentTypeCell.propTypes = {
AssignmentTypeCell.defaultProps = { AssignmentTypeCell.defaultProps = {
footnoteId: '', footnoteId: '',
footnoteMarker: '', footnoteMarker: null,
}; };
export default AssignmentTypeCell; export default AssignmentTypeCell;

View File

@@ -15,7 +15,7 @@ function DroppableAssignmentFootnote({ footnotes, intl }) {
<sup>{index + 1}</sup> <sup>{index + 1}</sup>
<FormattedMessage <FormattedMessage
id="progress.footnotes.droppableAssignments" id="progress.footnotes.droppableAssignments"
defaultMessage="The lowest {numDroppable, plural, one{# {assignmentType} score} other{# {assignmentType} scores}} will be dropped." defaultMessage="The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped."
values={{ values={{
numDroppable: footnote.numDroppable, numDroppable: footnote.numDroppable,
assignmentType: footnote.assignmentType, assignmentType: footnote.assignmentType,

View File

@@ -11,7 +11,6 @@ function GradeSummary() {
} = useSelector(state => state.courseHome); } = useSelector(state => state.courseHome);
const { const {
sectionScores,
gradingPolicy: { gradingPolicy: {
assignmentPolicies, assignmentPolicies,
}, },
@@ -21,27 +20,10 @@ function GradeSummary() {
return null; return null;
} }
// accumulate grades for individual assignment types
const gradeByAssignmentType = {};
assignmentPolicies.forEach(assignment => {
gradeByAssignmentType[assignment.type] = { numPointsEarned: 0, numPointsPossible: 0 };
});
sectionScores.forEach((chapter) => {
chapter.subsections.forEach((subsection) => {
if (subsection.hasGradedAssignment) {
gradeByAssignmentType[subsection.assignmentType].numPointsEarned += subsection.numPointsEarned;
gradeByAssignmentType[subsection.assignmentType].numPointsPossible += subsection.numPointsPossible;
}
});
});
return ( return (
<section className="text-dark-700 mb-4"> <section className="text-dark-700 mb-4">
<GradeSummaryHeader /> <GradeSummaryHeader />
<GradeSummaryTable <GradeSummaryTable />
gradeByAssignmentType={gradeByAssignmentType}
/>
</section> </section>
); );
} }

View File

@@ -1,6 +1,5 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { DataTable } from '@edx/paragon'; import { DataTable } from '@edx/paragon';
@@ -12,9 +11,7 @@ import GradeSummaryTableFooter from './GradeSummaryTableFooter';
import messages from '../messages'; import messages from '../messages';
function GradeSummaryTable({ function GradeSummaryTable({ intl }) {
gradeByAssignmentType, intl,
}) {
const { const {
courseId, courseId,
} = useSelector(state => state.courseHome); } = useSelector(state => state.courseHome);
@@ -27,10 +24,6 @@ function GradeSummaryTable({
const footnotes = []; const footnotes = [];
const calculateWeightedGrade = (numPointsEarned, numPointsPossible, assignmentWeight) => (
numPointsPossible > 0 ? ((numPointsEarned * assignmentWeight * 100) / numPointsPossible).toFixed(0) : 0
);
const getFootnoteId = (assignment) => { const getFootnoteId = (assignment) => {
const footnoteId = assignment.shortLabel ? assignment.shortLabel : assignment.type; const footnoteId = assignment.shortLabel ? assignment.shortLabel : assignment.type;
return footnoteId.replace(/[^A-Za-z0-9.-_]+/g, '-'); return footnoteId.replace(/[^A-Za-z0-9.-_]+/g, '-');
@@ -38,7 +31,7 @@ function GradeSummaryTable({
const gradeSummaryData = assignmentPolicies.map((assignment) => { const gradeSummaryData = assignmentPolicies.map((assignment) => {
let footnoteId = ''; let footnoteId = '';
let footnoteMarker = ''; let footnoteMarker;
if (assignment.numDroppable > 0) { if (assignment.numDroppable > 0) {
footnoteId = getFootnoteId(assignment); footnoteId = getFootnoteId(assignment);
@@ -51,17 +44,11 @@ function GradeSummaryTable({
footnoteMarker = footnotes.length; footnoteMarker = footnotes.length;
} }
const weightedGrade = calculateWeightedGrade(
gradeByAssignmentType[assignment.type].numPointsEarned,
gradeByAssignmentType[assignment.type].numPointsPossible,
assignment.weight,
);
return { return {
type: { footnoteId, footnoteMarker, type: assignment.type }, type: { footnoteId, footnoteMarker, type: assignment.type },
weight: `${assignment.weight * 100}%`, weight: `${assignment.weight * 100}%`,
score: `${gradeByAssignmentType[assignment.type].numPointsEarned}/${gradeByAssignmentType[assignment.type].numPointsPossible}`, grade: `${(assignment.averageGrade * 100).toFixed(0)}%`,
weightedGrade: `${weightedGrade}%`, weightedGrade: `${(assignment.weightedGrade * 100).toFixed(0)}%`,
}; };
}); });
@@ -91,8 +78,8 @@ function GradeSummaryTable({
cellClassName: 'float-right small', cellClassName: 'float-right small',
}, },
{ {
Header: `${intl.formatMessage(messages.score)}`, Header: `${intl.formatMessage(messages.grade)}`,
accessor: 'score', accessor: 'grade',
headerClassName: 'justify-content-end h5 mb-0', headerClassName: 'justify-content-end h5 mb-0',
cellClassName: 'float-right small', cellClassName: 'float-right small',
}, },
@@ -116,7 +103,6 @@ function GradeSummaryTable({
} }
GradeSummaryTable.propTypes = { GradeSummaryTable.propTypes = {
gradeByAssignmentType: PropTypes.shape({}).isRequired,
intl: intlShape.isRequired, intl: intlShape.isRequired,
}; };

View File

@@ -15,18 +15,18 @@ function GradeSummaryTableFooter({ intl }) {
const { const {
courseGrade: { courseGrade: {
isPassing, isPassing,
percent, visiblePercent,
}, },
} = useModel('progress', courseId); } = useModel('progress', courseId);
const bgColor = isPassing ? 'bg-success-100' : 'bg-warning-100'; const bgColor = isPassing ? 'bg-success-100' : 'bg-warning-100';
const totalGrade = percent * 100; const totalGrade = (visiblePercent * 100).toFixed(0);
return ( return (
<DataTable.TableFooter className={`border-top border-primary ${bgColor}`}> <DataTable.TableFooter className={`border-top border-primary ${bgColor}`}>
<div className="row w-100 m-0"> <div className="row w-100 m-0">
<div id="weighted-grade-summary" className="col-8 p-0 small">{intl.formatMessage(messages.weightedGradeSummary)}</div> <div id="weighted-grade-summary" className="col-8 p-0 small">{intl.formatMessage(messages.weightedGradeSummary)}</div>
<div aria-labelledby="weighted-grade-summary" className="col-4 p-0 text-right font-weight-bold small">{totalGrade}%</div> <div data-testid="gradeSummaryFooterTotalWeightedGrade" aria-labelledby="weighted-grade-summary" className="col-4 p-0 text-right font-weight-bold small">{totalGrade}%</div>
</div> </div>
</DataTable.TableFooter> </DataTable.TableFooter>
); );

View File

@@ -37,9 +37,13 @@ const messages = defineMessages({
id: 'progress.courseGrade.preview.header.ariaHidden', id: 'progress.courseGrade.preview.header.ariaHidden',
defaultMessage: 'Preview of a ', defaultMessage: 'Preview of a ',
}, },
courseGradePreviewBody: { courseGradePreviewUnlockCertificateBody: {
id: 'progress.courseGrade.preview.body', id: 'progress.courseGrade.preview.body.unlockCertificate',
defaultMessage: 'Unlock to view grades and work towards a certificate', defaultMessage: 'Unlock to view grades and work towards a certificate.',
},
courseGradePreviewUpgradeDeadlinePassedBody: {
id: 'progress.courseGrade.preview.body.upgradeDeadlinePassed',
defaultMessage: 'The deadline to upgrade in this course has passed.',
}, },
courseGradePreviewUpgradeButton: { courseGradePreviewUpgradeButton: {
id: 'progress.courseGrade.preview.button.upgrade', id: 'progress.courseGrade.preview.button.upgrade',
@@ -69,6 +73,10 @@ const messages = defineMessages({
id: 'progress.footnotes.title', id: 'progress.footnotes.title',
defaultMessage: 'Grade summary footnotes', defaultMessage: 'Grade summary footnotes',
}, },
grade: {
id: 'progress.gradeSummary.grade',
defaultMessage: 'Grade',
},
grades: { grades: {
id: 'progress.courseGrade.grades', id: 'progress.courseGrade.grades',
defaultMessage: 'Grades', defaultMessage: 'Grades',
@@ -88,7 +96,7 @@ const messages = defineMessages({
gradeSummaryTooltipBody: { gradeSummaryTooltipBody: {
id: 'progress.gradeSummary.tooltip.body', id: 'progress.gradeSummary.tooltip.body',
defaultMessage: "Your course assignment's weight is determined by your instructor. " defaultMessage: "Your course assignment's weight is determined by your instructor. "
+ 'By multiplying your score by the weight for that assignment type, your weighted grade is calculated. ' + 'By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. '
+ "Your weighted grade is what's used to determine if you pass the course.", + "Your weighted grade is what's used to determine if you pass the course.",
}, },
passingGradeLabel: { passingGradeLabel: {

View File

@@ -1,25 +1,47 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import messages from './messages'; import messages from './messages';
import { useModel } from '../../../generic/model-store';
function RelatedLinks({ intl }) { function RelatedLinks({ intl }) {
const { const {
courseId, courseId,
} = useSelector(state => state.courseHome); } = useSelector(state => state.courseHome);
const {
org,
} = useModel('courseHomeMeta', courseId);
const { administrator } = getAuthenticatedUser();
const logLinkClicked = (linkName) => {
sendTrackEvent('edx.ui.lms.course_progress.related_links.clicked', {
org_key: org,
courserun_key: courseId,
is_staff: administrator,
link_clicked: linkName,
});
};
return ( return (
<section className="mb-4 x-small"> <section className="mb-4 x-small">
<h3 className="h4">{intl.formatMessage(messages.relatedLinks)}</h3> <h3 className="h4">{intl.formatMessage(messages.relatedLinks)}</h3>
<ul className="pl-4"> <ul className="pl-4">
<li> <li>
<Link to={`course/${courseId}/dates`}>{intl.formatMessage(messages.datesCardLink)}</Link> <Hyperlink destination={`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`} onClick={() => logLinkClicked('dates')}>
{intl.formatMessage(messages.datesCardLink)}
</Hyperlink>
<p>{intl.formatMessage(messages.datesCardDescription)}</p> <p>{intl.formatMessage(messages.datesCardDescription)}</p>
</li> </li>
<li> <li>
<Link to={`course/${courseId}/home`}>{intl.formatMessage(messages.outlineCardLink)}</Link> <Hyperlink destination={`${getConfig().LMS_BASE_URL}/courses/${courseId}/course`} onClick={() => logLinkClicked('course_outline')}>
{intl.formatMessage(messages.outlineCardLink)}
</Hyperlink>
<p>{intl.formatMessage(messages.outlineCardDescription)}</p> <p>{intl.formatMessage(messages.outlineCardDescription)}</p>
</li> </li>
</ul> </ul>

View File

@@ -121,6 +121,8 @@ class CoursewareContainer extends Component {
sequenceId, sequenceId,
courseStatus, courseStatus,
sequenceStatus, sequenceStatus,
specialExamsEnabledWaffleFlag,
proctoredExamsEnabledWaffleFlag,
sequence, sequence,
firstSequenceId, firstSequenceId,
unitViaSequenceId, unitViaSequenceId,
@@ -176,7 +178,11 @@ class CoursewareContainer extends Component {
// Check special exam redirect: // Check special exam redirect:
// /course/:courseId/:sequenceId(/:unitId) -> :legacyWebUrl // /course/:courseId/:sequenceId(/:unitId) -> :legacyWebUrl
// because special exams are currently still served in the legacy LMS frontend. // because special exams are currently still served in the legacy LMS frontend.
checkSpecialExamRedirect(sequenceStatus, sequence); const shouldRedirectProctoredExams = specialExamsEnabledWaffleFlag && sequence.isProctored
&& !proctoredExamsEnabledWaffleFlag;
if (!specialExamsEnabledWaffleFlag || shouldRedirectProctoredExams) {
checkSpecialExamRedirect(sequenceStatus, sequence);
}
// Check to sequence to sequence-unit redirect: // Check to sequence to sequence-unit redirect:
// /course/:courseId/:sequenceId -> /course/:courseId/:sequenceId/:unitId // /course/:courseId/:sequenceId -> /course/:courseId/:sequenceId/:unitId
@@ -324,6 +330,7 @@ const sequenceShape = PropTypes.shape({
unitIds: PropTypes.arrayOf(PropTypes.string).isRequired, unitIds: PropTypes.arrayOf(PropTypes.string).isRequired,
sectionId: PropTypes.string.isRequired, sectionId: PropTypes.string.isRequired,
isTimeLimited: PropTypes.bool, isTimeLimited: PropTypes.bool,
isProctored: PropTypes.bool,
legacyWebUrl: PropTypes.string, legacyWebUrl: PropTypes.string,
}); });
@@ -362,6 +369,8 @@ CoursewareContainer.propTypes = {
checkBlockCompletion: PropTypes.func.isRequired, checkBlockCompletion: PropTypes.func.isRequired,
fetchCourse: PropTypes.func.isRequired, fetchCourse: PropTypes.func.isRequired,
fetchSequence: PropTypes.func.isRequired, fetchSequence: PropTypes.func.isRequired,
specialExamsEnabledWaffleFlag: PropTypes.bool.isRequired,
proctoredExamsEnabledWaffleFlag: PropTypes.bool.isRequired,
}; };
CoursewareContainer.defaultProps = { CoursewareContainer.defaultProps = {
@@ -461,7 +470,12 @@ const unitViaSequenceIdSelector = createSelector(
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { const {
courseId, sequenceId, courseStatus, sequenceStatus, courseId,
sequenceId,
courseStatus,
sequenceStatus,
specialExamsEnabledWaffleFlag,
proctoredExamsEnabledWaffleFlag,
} = state.courseware; } = state.courseware;
return { return {
@@ -469,6 +483,8 @@ const mapStateToProps = (state) => {
sequenceId, sequenceId,
courseStatus, courseStatus,
sequenceStatus, sequenceStatus,
specialExamsEnabledWaffleFlag,
proctoredExamsEnabledWaffleFlag,
course: currentCourseSelector(state), course: currentCourseSelector(state),
sequence: currentSequenceSelector(state), sequence: currentSequenceSelector(state),
previousSequence: previousSequenceSelector(state), previousSequence: previousSequenceSelector(state),

View File

@@ -127,6 +127,8 @@ describe('CoursewareContainer', () => {
sequenceMetadatas.forEach(sequenceMetadata => { sequenceMetadatas.forEach(sequenceMetadata => {
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceMetadata.item_id}`; const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${sequenceMetadata.item_id}`;
axiosMock.onGet(sequenceMetadataUrl).reply(200, sequenceMetadata); axiosMock.onGet(sequenceMetadataUrl).reply(200, sequenceMetadata);
const proctoredExamApiUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/proctored_exam/attempt/course_id/${courseId}/content_id/${sequenceMetadata.item_id}?is_learning_mfe=true`;
axiosMock.onGet(proctoredExamApiUrl).reply(200, { exam: {}, active_attempt: {} });
}); });
} }

View File

@@ -37,7 +37,7 @@ describe('Course', () => {
it('loads learning sequence', async () => { it('loads learning sequence', async () => {
render(<Course {...mockData} />); render(<Course {...mockData} />);
expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument(); expect(screen.getByRole('navigation', { name: 'breadcrumb' })).toBeInTheDocument();
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument(); expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
expect(screen.queryByRole('alert')).not.toBeInTheDocument(); expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); expect(screen.queryByRole('dialog')).not.toBeInTheDocument();

View File

@@ -60,6 +60,7 @@ function CourseCelebration({ intl }) {
certStatus, certStatus,
certWebViewUrl, certWebViewUrl,
downloadUrl, downloadUrl,
certificateAvailableDate,
} = certificateData || {}; } = certificateData || {};
/** [WS-1681 experiment] */ /** [WS-1681 experiment] */
@@ -125,25 +126,20 @@ function CourseCelebration({ intl }) {
break; break;
case 'earned_but_not_available': { case 'earned_but_not_available': {
const endDate = <FormattedDate value={end} day="numeric" month="long" year="numeric" />; const endDate = <FormattedDate value={end} day="numeric" month="long" year="numeric" />;
const certAvailableDate = <FormattedDate value={certificateAvailableDate} day="numeric" month="long" year="numeric" />;
certHeader = intl.formatMessage(messages.certificateHeaderNotAvailable); certHeader = intl.formatMessage(messages.certificateHeaderNotAvailable);
message = ( message = (
<> <>
<p> <p>
<FormattedMessage <FormattedMessage
id="courseCelebration.certificateBody.notAvailable.endDate" id="courseCelebration.certificateBody.notAvailable.endDate"
defaultMessage="After this course officially ends on {endDate}, you will receive an defaultMessage="This course ended on {endDate} and final grades and certificates are scheduled to be
email notification with your certificate. Once you have your certificate, be sure available after {certAvailableDate}."
to showcase your accomplishment on LinkedIn or your resumé." values={{ endDate, certAvailableDate }}
values={{ endDate }}
/> />
</p> </p>
<p> <p>
<FormattedMessage {intl.formatMessage(messages.certificateNotAvailableBodyAccessCert)}
id="courseCelebration.certificateBody.notAvailable.accessCertificate"
defaultMessage="You will be able to access your certificate any time from your
{dashboardLink} and {profileLink}."
values={{ dashboardLink, profileLink }}
/>
</p> </p>
</> </>
); );
@@ -294,7 +290,7 @@ function CourseCelebration({ intl }) {
</div> </div>
<div className="col-12 px-0 px-md-5"> <div className="col-12 px-0 px-md-5">
{certHeader && ( {certHeader && (
<Alert variant="primary" className="row w-100 m-0"> <Alert variant="success" className="row w-100 m-0">
<div className="col order-1 order-md-0 pl-0 pr-0 pr-md-5"> <div className="col order-1 order-md-0 pl-0 pr-0 pr-md-5">
<div className="h4">{certHeader}</div> <div className="h4">{certHeader}</div>
{message} {message}

View File

@@ -115,9 +115,14 @@ describe('Course Exit Pages', () => {
}); });
it('Displays certificate is earned but unavailable message', async () => { it('Displays certificate is earned but unavailable message', async () => {
setMetadata({ certificate_data: { cert_status: 'earned_but_not_available' } }); setMetadata({
certificate_data: {
cert_status: 'earned_but_not_available',
certificate_available_date: '2021-05-21T12:00:00Z',
},
});
await fetchAndRender(<CourseCelebration />); await fetchAndRender(<CourseCelebration />);
expect(screen.getByText('Your certificate will be available soon!')).toBeInTheDocument(); expect(screen.getByText('Your grade and certificate will be ready soon!')).toBeInTheDocument();
}); });
it('Displays request certificate link', async () => { it('Displays request certificate link', async () => {

View File

@@ -13,7 +13,12 @@ const messages = defineMessages({
}, },
certificateHeaderNotAvailable: { certificateHeaderNotAvailable: {
id: 'courseCelebration.certificateHeader.notAvailable', id: 'courseCelebration.certificateHeader.notAvailable',
defaultMessage: 'Your certificate will be available soon!', defaultMessage: 'Your grade and certificate will be ready soon!',
description: 'Header displayed when course certificate is not yet available to be viewed',
},
certificateNotAvailableBodyAccessCert: {
id: 'courseCelebration.certificateBody.notAvailable.accessCertificate',
defaultMessage: 'If you have earned a passing grade, your certificate will be automatically issued.',
description: 'Text displayed when course certificate is not yet available to be viewed', description: 'Text displayed when course certificate is not yet available to be viewed',
}, },
certificateHeaderUnverified: { certificateHeaderUnverified: {
@@ -180,6 +185,7 @@ const messages = defineMessages({
id: 'courseExit.viewGradesButton', id: 'courseExit.viewGradesButton',
defaultMessage: 'View grades', defaultMessage: 'View grades',
}, },
}); });
export default messages; export default messages;

View File

@@ -11,6 +11,7 @@ import {
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { history } from '@edx/frontend-platform'; import { history } from '@edx/frontend-platform';
import SequenceExamWrapper from '@edx/frontend-lib-special-exams';
import PageLoading from '../../../generic/PageLoading'; import PageLoading from '../../../generic/PageLoading';
import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages'; import { UserMessagesContext, ALERT_TYPES } from '../../../generic/user-messages';
@@ -46,6 +47,8 @@ function Sequence({
const sequence = useModel('sequences', sequenceId); const sequence = useModel('sequences', sequenceId);
const unit = useModel('units', unitId); const unit = useModel('units', unitId);
const sequenceStatus = useSelector(state => state.courseware.sequenceStatus); const sequenceStatus = useSelector(state => state.courseware.sequenceStatus);
const specialExamsEnabledWaffleFlag = useSelector(state => state.courseware.specialExamsEnabledWaffleFlag);
const proctoredExamsEnabledWaffleFlag = useSelector(state => state.courseware.proctoredExamsEnabledWaffleFlag);
const shouldDisplaySidebarButton = useWindowSize().width < responsiveBreakpoints.small.minWidth; const shouldDisplaySidebarButton = useWindowSize().width < responsiveBreakpoints.small.minWidth;
const handleNext = () => { const handleNext = () => {
@@ -115,11 +118,16 @@ function Sequence({
const handleUnitLoaded = () => { const handleUnitLoaded = () => {
setUnitHasLoaded(true); setUnitHasLoaded(true);
}; };
// We want hide the unit navigation if we're in the middle of navigating to another unit
// but not if other things about the unit change, like the bookmark status.
// The array property of this useEffect ensures that we only hide the unit navigation
// while navigating to another unit.
useEffect(() => { useEffect(() => {
if (unit) { if (unit) {
setUnitHasLoaded(false); setUnitHasLoaded(false);
} }
}, [unit]); }, [(unit || {}).id]);
if (sequenceStatus === 'loading') { if (sequenceStatus === 'loading') {
if (!sequenceId) { if (!sequenceId) {
@@ -138,12 +146,18 @@ function Sequence({
because we expect CoursewareContainer to be performing a redirect to the legacy experience while because we expect CoursewareContainer to be performing a redirect to the legacy experience while
we're waiting. That redirect may take a few seconds, so we show the spinner in the meantime. we're waiting. That redirect may take a few seconds, so we show the spinner in the meantime.
*/ */
if (sequenceStatus === 'loaded' && sequence.isTimeLimited) { if (sequenceStatus === 'loaded') {
return ( const shouldRedirectSpecialExams = sequence.isTimeLimited && !specialExamsEnabledWaffleFlag;
<PageLoading const shouldRedirectProctoredExams = sequence.isProctored && specialExamsEnabledWaffleFlag
srMessage={intl.formatMessage(messages['learn.loading.learning.sequence'])} && !proctoredExamsEnabledWaffleFlag;
/>
); if (shouldRedirectSpecialExams || shouldRedirectProctoredExams) {
return (
<PageLoading
srMessage={intl.formatMessage(messages['learn.loading.learning.sequence'])}
/>
);
}
} }
const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated; const gated = sequence && sequence.gatedContent !== undefined && sequence.gatedContent.gated;
@@ -151,83 +165,89 @@ function Sequence({
history.push(`/course/${courseId}/course-end`); history.push(`/course/${courseId}/course-end`);
}; };
const defaultContent = (
<div className="sequence-container" style={{ display: 'inline-flex', flexDirection: 'row' }}>
<div className={classNames('sequence', { 'position-relative': shouldDisplaySidebarButton })} style={{ width: '100%' }}>
<SequenceNavigation
sequenceId={sequenceId}
unitId={unitId}
className="mb-4"
/** [MM-P2P] Experiment */
mmp2p={mmp2p}
nextSequenceHandler={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'top');
handleNext();
}}
onNavigate={(destinationUnitId) => {
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
handleNavigate(destinationUnitId);
}}
previousSequenceHandler={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
handlePrevious();
}}
goToCourseExitPage={() => goToCourseExitPage()}
isValuePropCookieSet={isValuePropCookieSet}
/>
{isValuePropCookieSet && shouldDisplaySidebarButton ? (
<SidebarNotificationButton
toggleSidebar={toggleSidebar}
isSidebarVisible={isSidebarVisible}
/>
) : null}
<div className="unit-container flex-grow-1">
<SequenceContent
courseId={courseId}
gated={gated}
sequenceId={sequenceId}
unitId={unitId}
unitLoadedHandler={handleUnitLoaded}
/** [MM-P2P] Experiment */
mmp2p={mmp2p}
/>
{unitHasLoaded && (
<UnitNavigation
sequenceId={sequenceId}
unitId={unitId}
onClickPrevious={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'bottom');
handlePrevious();
}}
onClickNext={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'bottom');
handleNext();
}}
goToCourseExitPage={() => goToCourseExitPage()}
/>
)}
</div>
</div>
{sidebarVisible ? (
<Sidebar
toggleSidebar={toggleSidebar}
sidebarVisible={sidebarVisible}
/>
) : null }
{/** [MM-P2P] Experiment */}
{(mmp2p.state.isEnabled && mmp2p.flyover.isVisible) && (
isMobile()
? <MMP2PFlyoverMobile options={mmp2p} />
: <MMP2PFlyover options={mmp2p} />
)}
</div>
);
if (sequenceStatus === 'loaded') { if (sequenceStatus === 'loaded') {
return ( return (
<div> <div>
<div className="sequence-container" style={{ display: 'inline-flex', flexDirection: 'row' }}> <SequenceExamWrapper sequence={sequence} courseId={courseId}>
<div className={classNames('sequence', { 'position-relative': shouldDisplaySidebarButton })} style={{ width: '100%' }}> {defaultContent}
<SequenceNavigation </SequenceExamWrapper>
sequenceId={sequenceId}
unitId={unitId}
className="mb-4"
/** [MM-P2P] Experiment */
mmp2p={mmp2p}
nextSequenceHandler={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'top');
handleNext();
}}
onNavigate={(destinationUnitId) => {
logEvent('edx.ui.lms.sequence.tab_selected', 'top', destinationUnitId);
handleNavigate(destinationUnitId);
}}
previousSequenceHandler={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'top');
handlePrevious();
}}
goToCourseExitPage={() => goToCourseExitPage()}
isValuePropCookieSet={isValuePropCookieSet}
/>
{isValuePropCookieSet && shouldDisplaySidebarButton ? (
<SidebarNotificationButton
toggleSidebar={toggleSidebar}
isSidebarVisible={isSidebarVisible}
/>
) : null}
<div className="unit-container flex-grow-1">
<SequenceContent
courseId={courseId}
gated={gated}
sequenceId={sequenceId}
unitId={unitId}
unitLoadedHandler={handleUnitLoaded}
/** [MM-P2P] Experiment */
mmp2p={mmp2p}
/>
{unitHasLoaded && (
<UnitNavigation
sequenceId={sequenceId}
unitId={unitId}
onClickPrevious={() => {
logEvent('edx.ui.lms.sequence.previous_selected', 'bottom');
handlePrevious();
}}
onClickNext={() => {
logEvent('edx.ui.lms.sequence.next_selected', 'bottom');
handleNext();
}}
goToCourseExitPage={() => goToCourseExitPage()}
/>
)}
</div>
</div>
{sidebarVisible ? (
<Sidebar
toggleSidebar={toggleSidebar}
sidebarVisible={sidebarVisible}
/>
) : null }
{/** [MM-P2P] Experiment */}
{(mmp2p.state.isEnabled && mmp2p.flyover.isVisible) && (
isMobile()
? <MMP2PFlyoverMobile options={mmp2p} />
: <MMP2PFlyover options={mmp2p} />
)}
</div>
<CourseLicense license={course.license || undefined} /> <CourseLicense license={course.license || undefined} />
</div> </div>
); );

View File

@@ -67,11 +67,11 @@ describe('Sequence', () => {
{ store: testStore }, { store: testStore },
); );
expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument(); await waitFor(() => expect(screen.queryByText('Loading locked content messaging...')).toBeInTheDocument());
// Only `Previous`, `Next` and `Bookmark` buttons. // `Previous`, `Active`, `Next` and `Prerequisite` buttons.
expect(screen.getAllByRole('button').length).toEqual(3); expect(screen.getAllByRole('button').length).toEqual(4);
expect(await screen.findByText('Content Locked')).toBeInTheDocument(); expect(screen.getByText('Content Locked')).toBeInTheDocument();
const unitContainer = container.querySelector('.unit-container'); const unitContainer = container.querySelector('.unit-container');
expect(unitContainer.querySelector('svg')).toHaveClass('fa-lock'); expect(unitContainer.querySelector('svg')).toHaveClass('fa-lock');
expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument(); expect(screen.getByText(/You must complete the prerequisite/)).toBeInTheDocument();
@@ -120,7 +120,7 @@ describe('Sequence', () => {
it('handles loading unit', async () => { it('handles loading unit', async () => {
render(<Sequence {...mockData} />); render(<Sequence {...mockData} />);
expect(screen.getByText('Loading learning sequence...')).toBeInTheDocument(); expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
// Renders navigation buttons plus one button for each unit. // Renders navigation buttons plus one button for each unit.
expect(screen.getAllByRole('button')).toHaveLength(3 + unitBlocks.length); expect(screen.getAllByRole('button')).toHaveLength(3 + unitBlocks.length);
@@ -167,6 +167,7 @@ describe('Sequence', () => {
previousSequenceHandler: jest.fn(), previousSequenceHandler: jest.fn(),
}; };
render(<Sequence {...testData} />, { store: testStore }); render(<Sequence {...testData} />, { store: testStore });
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
const sequencePreviousButton = screen.getByRole('button', { name: /previous/i }); const sequencePreviousButton = screen.getByRole('button', { name: /previous/i });
fireEvent.click(sequencePreviousButton); fireEvent.click(sequencePreviousButton);
@@ -202,6 +203,7 @@ describe('Sequence', () => {
nextSequenceHandler: jest.fn(), nextSequenceHandler: jest.fn(),
}; };
render(<Sequence {...testData} />, { store: testStore }); render(<Sequence {...testData} />, { store: testStore });
expect(await screen.findByText('Loading learning sequence...')).toBeInTheDocument();
const sequenceNextButton = screen.getByRole('button', { name: /next/i }); const sequenceNextButton = screen.getByRole('button', { name: /next/i });
fireEvent.click(sequenceNextButton); fireEvent.click(sequenceNextButton);
@@ -228,7 +230,7 @@ describe('Sequence', () => {
}); });
}); });
it('navigates to the previous/next unit if the unit is not in the corner of the sequence', () => { it('navigates to the previous/next unit if the unit is not in the corner of the sequence', async () => {
const unitNumber = 1; const unitNumber = 1;
const testData = { const testData = {
...mockData, ...mockData,
@@ -239,6 +241,7 @@ describe('Sequence', () => {
nextSequenceHandler: jest.fn(), nextSequenceHandler: jest.fn(),
}; };
render(<Sequence {...testData} />, { store: testStore }); render(<Sequence {...testData} />, { store: testStore });
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).toBeInTheDocument());
fireEvent.click(screen.getByRole('button', { name: /previous/i })); fireEvent.click(screen.getByRole('button', { name: /previous/i }));
expect(testData.previousSequenceHandler).not.toHaveBeenCalled(); expect(testData.previousSequenceHandler).not.toHaveBeenCalled();
@@ -360,7 +363,7 @@ describe('Sequence', () => {
}); });
}); });
it('handles unit navigation button', () => { it('handles unit navigation button', async () => {
const currentTabNumber = 1; const currentTabNumber = 1;
const targetUnitNumber = 2; const targetUnitNumber = 2;
const targetUnit = unitBlocks[targetUnitNumber - 1]; const targetUnit = unitBlocks[targetUnitNumber - 1];
@@ -371,6 +374,7 @@ describe('Sequence', () => {
unitNavigationHandler: jest.fn(), unitNavigationHandler: jest.fn(),
}; };
render(<Sequence {...testData} />, { store: testStore }); render(<Sequence {...testData} />, { store: testStore });
await waitFor(() => expect(screen.queryByText('Loading learning sequence...')).toBeInTheDocument());
fireEvent.click(screen.getByRole('button', { name: targetUnit.display_name })); fireEvent.click(screen.getByRole('button', { name: targetUnit.display_name }));
expect(testData.unitNavigationHandler).toHaveBeenCalledWith(targetUnit.id); expect(testData.unitNavigationHandler).toHaveBeenCalledWith(targetUnit.id);

View File

@@ -21,6 +21,7 @@ import { fetchCourse } from '../../data/thunks';
/** [MM-P2P] Experiment */ /** [MM-P2P] Experiment */
import { MMP2PLockPaywall } from '../../../experiments/mm-p2p'; import { MMP2PLockPaywall } from '../../../experiments/mm-p2p';
const HonorCode = React.lazy(() => import('./honor-code'));
const LockPaywall = React.lazy(() => import('./lock-paywall')); const LockPaywall = React.lazy(() => import('./lock-paywall'));
/** /**
@@ -93,6 +94,7 @@ function Unit({
const course = useModel('coursewareMeta', courseId); const course = useModel('coursewareMeta', courseId);
const { const {
contentTypeGatingEnabled, contentTypeGatingEnabled,
userNeedsIntegritySignature,
} = course; } = course;
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -116,6 +118,10 @@ function Unit({
} else if (type === 'plugin.modal') { } else if (type === 'plugin.modal') {
payload.open = true; payload.open = true;
setModalOptions(payload); setModalOptions(payload);
} else if (event.data.offset) {
// We listen for this message from LMS to know when the page needs to
// be scroll to another location on the page.
window.scrollTo(0, event.data.offset);
} }
} }
// If we currently have an event listener, remove it. // If we currently have an event listener, remove it.
@@ -133,7 +139,7 @@ function Unit({
return ( return (
<div className="unit"> <div className="unit">
<h2 className="mb-0 h3">{unit.title}</h2> <h1 className="mb-0 h3">{unit.title}</h1>
<BookmarkButton <BookmarkButton
unitId={unit.id} unitId={unit.id}
isBookmarked={unit.bookmarked} isBookmarked={unit.bookmarked}
@@ -154,8 +160,19 @@ function Unit({
{ mmp2p.meta.showLock && ( { mmp2p.meta.showLock && (
<MMP2PLockPaywall options={mmp2p} /> <MMP2PLockPaywall options={mmp2p} />
)} )}
{!mmp2p.meta.blockContent && unit.graded && userNeedsIntegritySignature && (
<Suspense
fallback={(
<PageLoading
srMessage={intl.formatMessage(messages['learn.loading.honor.code'])}
/>
)}
>
<HonorCode courseId={courseId} />
</Suspense>
)}
{ /** [MM-P2P] Experiment (conditional) */ } { /** [MM-P2P] Experiment (conditional) */ }
{!mmp2p.meta.blockContent && !hasLoaded && ( {!mmp2p.meta.blockContent && !userNeedsIntegritySignature && !hasLoaded && (
<PageLoading <PageLoading
srMessage={intl.formatMessage(messages['learn.loading.learning.sequence'])} srMessage={intl.formatMessage(messages['learn.loading.learning.sequence'])}
/> />
@@ -186,7 +203,7 @@ function Unit({
/> />
)} )}
{ /** [MM-P2P] Experiment (conditional) */ } { /** [MM-P2P] Experiment (conditional) */ }
{ !mmp2p.meta.blockContent && ( { !mmp2p.meta.blockContent && !userNeedsIntegritySignature && (
<div className="unit-iframe-wrapper"> <div className="unit-iframe-wrapper">
<iframe <iframe
id="unit-iframe" id="unit-iframe"

View File

@@ -13,11 +13,16 @@ describe('Unit', () => {
); );
const unitBlocks = [Factory.build( const unitBlocks = [Factory.build(
'block', 'block',
{ type: 'problem' }, { type: 'problem', graded: 'true' },
{ courseId: courseMetadata.id }, { courseId: courseMetadata.id },
), Factory.build( ), Factory.build(
'block', 'block',
{ type: 'vertical', contains_content_type_gated_content: true, bookmarked: true }, {
type: 'vertical',
contains_content_type_gated_content: true,
bookmarked: true,
graded: true,
},
{ courseId: courseMetadata.id }, { courseId: courseMetadata.id },
)]; )];
const [unit, unitThatContainsGatedContent] = unitBlocks; const [unit, unitThatContainsGatedContent] = unitBlocks;
@@ -49,6 +54,24 @@ describe('Unit', () => {
expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument(); expect(screen.getByText('Loading locked content messaging...')).toBeInTheDocument();
}); });
it('displays HonorCode when userNeedsIntegritySignature is true', async () => {
const signatureMetadata = Factory.build(
'courseMetadata',
{ user_needs_integrity_signature: true },
);
const signatureStore = await initializeTestStore(
{ courseMetadata: signatureMetadata, unitBlocks },
false,
);
const signatureData = {
id: unit.id,
courseId: signatureMetadata.id,
format: 'Homework',
};
render(<Unit {...signatureData} />, { store: signatureStore });
expect(screen.getByText('Loading honor code messaging...')).toBeInTheDocument();
});
it('handles receiving MessageEvent', async () => { it('handles receiving MessageEvent', async () => {
render(<Unit {...mockData} />); render(<Unit {...mockData} />);
loadUnit(); loadUnit();
@@ -80,6 +103,16 @@ describe('Unit', () => {
expect(onLoaded).toHaveBeenCalledTimes(1); expect(onLoaded).toHaveBeenCalledTimes(1);
}); });
it('scrolls page on MessagaeEvent when receiving offset', async () => {
// Set message to constain offset data.
const testMessageWithOffset = { offset: 1500 };
render(<Unit {...mockData} />);
window.postMessage(testMessageWithOffset, '*');
await expect(waitFor(() => expect(window.scrollTo()).toHaveBeenCalled()));
expect(window.scrollY === testMessageWithOffset.offset);
});
it('ignores MessageEvent with unhandled type', async () => { it('ignores MessageEvent with unhandled type', async () => {
// Clone message and set different type. // Clone message and set different type.
const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' }; const testMessageWithUnhandledType = { ...messageEvent, type: 'wrong type' };

View File

@@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { getConfig, history } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ActionRow, Alert, Button } from '@edx/paragon';
import { saveIntegritySignature } from '../../../data';
import messages from './messages';
function HonorCode({ intl, courseId }) {
const dispatch = useDispatch();
const siteName = getConfig().SITE_NAME;
const honorCodeUrl = `${process.env.TERMS_OF_SERVICE_URL}#honor-code`;
const handleCancel = () => history.push(`/course/${courseId}/home`);
const handleAgree = () => {
dispatch(saveIntegritySignature(courseId));
};
return (
<Alert variant="light" aria-live="off">
<h4 aria-level="3">
{siteName}{' '}
{intl.formatMessage(messages['learn.honorCode.name'])}
</h4>
<p>
<FormattedMessage
id="learn.honorCode.content"
defaultMessage="Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site."
values={{
siteName,
link: <a href={honorCodeUrl}>{intl.formatMessage(messages['learn.honorCode.name'])}</a>,
}}
/>
</p>
<ActionRow>
<ActionRow.Spacer />
<Button variant="tertiary" onClick={handleCancel}>
{intl.formatMessage(messages['learn.honorCode.cancel'])}
</Button>
<Button variant="primary" onClick={handleAgree}>
{intl.formatMessage(messages['learn.honorCode.agree'])}
</Button>
</ActionRow>
</Alert>
);
}
HonorCode.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
};
export default injectIntl(HonorCode);

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { history } from '@edx/frontend-platform';
import {
fireEvent, initializeTestStore, render, screen,
} from '../../../../setupTest';
import HonorCode from './HonorCode';
jest.mock('@edx/frontend-platform', () => ({
...jest.requireActual('@edx/frontend-platform'),
history: {
push: jest.fn(),
},
}));
describe('Honor Code', () => {
let store;
const mockData = {};
beforeAll(async () => {
store = await initializeTestStore();
const { courseware } = store.getState();
mockData.courseId = courseware.courseId;
});
it('cancel button links to course home ', () => {
render(<HonorCode {...mockData} />);
const cancelButton = screen.getByText('Cancel');
fireEvent.click(cancelButton);
expect(history.push).toHaveBeenCalledWith(`/course/${mockData.courseId}/home`);
});
});

View File

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

View File

@@ -0,0 +1,21 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'learn.honorCode.name': {
id: 'learn.honorCode.name',
defaultMessage: 'Honor Code',
description: 'Honor code name.',
},
'learn.honorCode.cancel': {
id: 'learn.honorCode.cancel',
defaultMessage: 'Cancel',
description: '"Cancel" button.',
},
'learn.honorCode.agree': {
id: 'learn.honorCode.agree',
defaultMessage: 'I agree',
description: '"I agree" button.',
},
});
export default messages;

View File

@@ -5,12 +5,13 @@ import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { import {
Alert, Button, Icon, Alert, Icon,
} from '@edx/paragon'; } from '@edx/paragon';
import { Locked } from '@edx/paragon/icons'; import { Locked } from '@edx/paragon/icons';
import messages from './messages'; import messages from './messages';
import certificateLocked from '../../../../generic/assets/edX_locked_certificate.png'; import certificateLocked from '../../../../generic/assets/edX_locked_certificate.png';
import { useModel } from '../../../../generic/model-store'; import { useModel } from '../../../../generic/model-store';
import { UpgradeButton } from '../../../../generic/upgrade-button';
import './LockPaywall.scss'; import './LockPaywall.scss';
function LockPaywall({ function LockPaywall({
@@ -19,6 +20,7 @@ function LockPaywall({
}) { }) {
const course = useModel('coursewareMeta', courseId); const course = useModel('coursewareMeta', courseId);
const { const {
offer,
org, org,
verifiedMode, verifiedMode,
} = course; } = course;
@@ -26,11 +28,6 @@ function LockPaywall({
if (!verifiedMode) { if (!verifiedMode) {
return null; return null;
} }
const {
currencySymbol,
price,
upgradeUrl,
} = verifiedMode;
const eventProperties = { const eventProperties = {
org_key: org, org_key: org,
@@ -75,7 +72,7 @@ function LockPaywall({
{intl.formatMessage(messages['learn.lockPaywall.list.bullet3.boldtext'])} {intl.formatMessage(messages['learn.lockPaywall.list.bullet3.boldtext'])}
</span> </span>
); );
const nonProfit = ( const nonProfitMission = (
<span className="font-weight-bold"> <span className="font-weight-bold">
{intl.formatMessage(messages['learn.lockPaywall.list.bullet4.boldtext'])} {intl.formatMessage(messages['learn.lockPaywall.list.bullet4.boldtext'])}
</span> </span>
@@ -141,8 +138,8 @@ function LockPaywall({
<span className="fa-li"><FontAwesomeIcon icon={faCheck} /></span> <span className="fa-li"><FontAwesomeIcon icon={faCheck} /></span>
<FormattedMessage <FormattedMessage
id="gatedContent.paragraph.bulletFour" id="gatedContent.paragraph.bulletFour"
defaultMessage="Support our {nonProfit} mission at edX" defaultMessage="Support our {nonProfitMission} at edX"
values={{ nonProfit }} values={{ nonProfitMission }}
/> />
</li> </li>
</ul> </ul>
@@ -154,17 +151,11 @@ function LockPaywall({
className="col-md-auto p-md-0 d-md-flex align-items-md-center mr-md-3" className="col-md-auto p-md-0 d-md-flex align-items-md-center mr-md-3"
style={{ textAlign: 'right' }} style={{ textAlign: 'right' }}
> >
<Button <UpgradeButton
className="lock_paywall_upgrade_link" offer={offer}
href={upgradeUrl}
onClick={logClick} onClick={logClick}
role="link" verifiedMode={verifiedMode}
> />
{intl.formatMessage(messages['learn.lockPaywall.upgrade.link'], {
currencySymbol,
price,
})}
</Button>
</div> </div>
</div> </div>
</Alert> </Alert>

View File

@@ -11,12 +11,14 @@ jest.mock('@edx/frontend-platform/analytics');
describe('Lock Paywall', () => { describe('Lock Paywall', () => {
let store; let store;
const mockData = {}; const mockData = { metadataModel: 'coursewareMeta' };
beforeAll(async () => { beforeAll(async () => {
store = await initializeTestStore(); store = await initializeTestStore();
const { courseware } = store.getState(); const { courseware } = store.getState();
mockData.courseId = courseware.courseId; Object.assign(mockData, {
courseId: courseware.courseId,
});
}); });
it('displays unlock link with price', () => { it('displays unlock link with price', () => {
@@ -31,6 +33,23 @@ describe('Lock Paywall', () => {
expect(upgradeLink).toHaveAttribute('href', `${upgradeUrl}`); expect(upgradeLink).toHaveAttribute('href', `${upgradeUrl}`);
}); });
it('displays discounted price if there is an offer/first time purchase', async () => {
const courseMetadata = Factory.build('courseMetadata', {
offer: {
code: 'EDXWELCOME',
expiration_date: '2070-01-01T12:00:00Z',
original_price: '$100',
discounted_price: '$85',
percentage: 15,
upgrade_url: 'https://example.com/upgrade',
},
});
const testStore = await initializeTestStore({ courseMetadata }, false);
render(<LockPaywall {...mockData} courseId={courseMetadata.id} />, { store: testStore });
expect(screen.getByText(/Upgrade for/).textContent).toMatch('$85 ($100)');
});
it('sends analytics event onClick of unlock link', () => { it('sends analytics event onClick of unlock link', () => {
sendTrackEvent.mockClear(); sendTrackEvent.mockClear();

View File

@@ -11,11 +11,6 @@ const messages = defineMessages({
defaultMessage: 'Upgrade to gain access to locked features like this one and get the most out of your course.', defaultMessage: 'Upgrade to gain access to locked features like this one and get the most out of your course.',
description: 'Message shown to indicate that a piece of content is unavailable to audit track users.', description: 'Message shown to indicate that a piece of content is unavailable to audit track users.',
}, },
'learn.lockPaywall.upgrade.link': {
id: 'learn.lockPaywall.upgrade.link',
defaultMessage: 'Upgrade for {currencySymbol}{price}',
description: 'A link users can click that navigates their browser to the upgrade payment page.',
},
'learn.lockPaywall.example.alt': { 'learn.lockPaywall.example.alt': {
id: 'learn.lockPaywall.example.alt', id: 'learn.lockPaywall.example.alt',
defaultMessage: 'Example Certificate', defaultMessage: 'Example Certificate',
@@ -43,7 +38,7 @@ const messages = defineMessages({
}, },
'learn.lockPaywall.list.bullet4.boldtext': { 'learn.lockPaywall.list.bullet4.boldtext': {
id: 'learn.lockPaywall.list.bullet4.boldtext', id: 'learn.lockPaywall.list.bullet4.boldtext',
defaultMessage: 'non-profit', defaultMessage: 'non-profit mission',
description: 'Bolded text to highlight our non-profit status.', description: 'Bolded text to highlight our non-profit status.',
}, },
}); });

View File

@@ -6,6 +6,11 @@ const messages = defineMessages({
defaultMessage: 'Loading locked content messaging...', defaultMessage: 'Loading locked content messaging...',
description: 'Message shown when an interface about locked content is being loaded', description: 'Message shown when an interface about locked content is being loaded',
}, },
'learn.loading.honor.code': {
id: 'learn.loading.honor.codk',
defaultMessage: 'Loading honor code messaging...',
description: 'Message shown when an interface about the honor code is being loaded',
},
'learn.loading.learning.sequence': { 'learn.loading.learning.sequence': {
id: 'learn.loading.learning.sequence', id: 'learn.loading.learning.sequence',
defaultMessage: 'Loading learning sequence...', defaultMessage: 'Loading learning sequence...',

View File

@@ -56,4 +56,7 @@ Factory.define('courseMetadata')
verification_status: 'none', verification_status: 'none',
linkedin_add_to_profile_url: null, linkedin_add_to_profile_url: null,
related_programs: null, related_programs: null,
user_needs_integrity_signature: false,
is_mfe_special_exams_enabled: false,
is_mfe_proctored_exams_enabled: false,
}); });

View File

@@ -151,6 +151,9 @@ function normalizeMetadata(metadata) {
verificationStatus: metadata.verification_status, verificationStatus: metadata.verification_status,
linkedinAddToProfileUrl: metadata.linkedin_add_to_profile_url, linkedinAddToProfileUrl: metadata.linkedin_add_to_profile_url,
relatedPrograms: camelCaseObject(metadata.related_programs), relatedPrograms: camelCaseObject(metadata.related_programs),
userNeedsIntegritySignature: metadata.user_needs_integrity_signature,
specialExamsEnabledWaffleFlag: metadata.is_mfe_special_exams_enabled,
proctoredExamsEnabledWaffleFlag: metadata.is_mfe_proctored_exams_enabled,
}; };
} }
@@ -181,10 +184,12 @@ function normalizeSequenceMetadata(sequence) {
*/ */
gatedContent: camelCaseObject(sequence.gated_content), gatedContent: camelCaseObject(sequence.gated_content),
isTimeLimited: sequence.is_time_limited, isTimeLimited: sequence.is_time_limited,
isProctored: sequence.is_proctored,
// Position comes back from the server 1-indexed. Adjust here. // Position comes back from the server 1-indexed. Adjust here.
activeUnitIndex: sequence.position ? sequence.position - 1 : 0, activeUnitIndex: sequence.position ? sequence.position - 1 : 0,
saveUnitPosition: sequence.save_position, saveUnitPosition: sequence.save_position,
showCompletion: sequence.show_completion, showCompletion: sequence.show_completion,
allowProctoringOptOut: sequence.allow_proctoring_opt_out,
}, },
units: sequence.items.map(unit => ({ units: sequence.items.map(unit => ({
id: unit.id, id: unit.id,
@@ -193,6 +198,7 @@ function normalizeSequenceMetadata(sequence) {
complete: unit.complete, complete: unit.complete,
title: unit.page_title, title: unit.page_title,
contentType: unit.type, contentType: unit.type,
graded: unit.graded,
containsContentTypeGatedContent: unit.contains_content_type_gated_content, containsContentTypeGatedContent: unit.contains_content_type_gated_content,
})), })),
}; };
@@ -229,3 +235,15 @@ export async function getResumeBlock(courseId) {
const { data } = await getAuthenticatedHttpClient().get(url.href, {}); const { data } = await getAuthenticatedHttpClient().get(url.href, {});
return camelCaseObject(data); return camelCaseObject(data);
} }
export async function postIntegritySignature(courseId) {
const { data } = await getAuthenticatedHttpClient().post(
`${getConfig().LMS_BASE_URL}/api/agreements/v1/integrity_signature/${courseId}`, {},
);
return camelCaseObject(data);
}
export async function sendActivationEmail() {
const url = new URL(`${getConfig().LMS_BASE_URL}/api/send_account_activation_email`);
const { data } = await getAuthenticatedHttpClient().post(url.href, {});
return data;
}

View File

@@ -2,10 +2,12 @@ export {
fetchCourse, fetchCourse,
fetchSequence, fetchSequence,
checkBlockCompletion, checkBlockCompletion,
saveIntegritySignature,
saveSequencePosition, saveSequencePosition,
} from './thunks'; } from './thunks';
export { export {
getResumeBlock, getResumeBlock,
sendActivationEmail,
} from './api'; } from './api';
export { export {
sequenceIdsSelector, sequenceIdsSelector,

View File

@@ -262,4 +262,33 @@ describe('Data layer integration tests', () => {
}); });
}); });
}); });
describe('test saveIntegritySignature', () => {
it('Should update userNeedsIntegritySignature upon success', async () => {
const courseMetadataNeedSignature = Factory.build('courseMetadata', {
user_needs_integrity_signature: true,
});
let courseUrlNeedSignature = `${courseBaseUrl}/${courseMetadataNeedSignature.id}`;
courseUrlNeedSignature = appendBrowserTimezoneToUrl(courseUrlNeedSignature);
axiosMock.onGet(courseUrlNeedSignature).reply(200, courseMetadataNeedSignature);
await executeThunk(thunks.fetchCourse(courseMetadataNeedSignature.id), store.dispatch);
expect(
store.getState().models.coursewareMeta[courseMetadataNeedSignature.id].userNeedsIntegritySignature,
).toEqual(true);
const integritySignatureUrl = `${getConfig().LMS_BASE_URL}/api/agreements/v1/integrity_signature/${courseMetadataNeedSignature.id}`;
axiosMock.onPost(integritySignatureUrl).reply(200, {});
await executeThunk(
thunks.saveIntegritySignature(courseMetadataNeedSignature.id),
store.dispatch,
store.getState,
);
expect(
store.getState().models.coursewareMeta[courseMetadataNeedSignature.id].userNeedsIntegritySignature,
).toEqual(false);
});
});
}); });

View File

@@ -13,8 +13,16 @@ const slice = createSlice({
courseId: null, courseId: null,
sequenceStatus: 'loading', sequenceStatus: 'loading',
sequenceId: null, sequenceId: null,
specialExamsEnabledWaffleFlag: false,
proctoredExamsEnabledWaffleFlag: false,
}, },
reducers: { reducers: {
setsSpecialExamsEnabled: (state, { payload }) => {
state.specialExamsEnabledWaffleFlag = payload.specialExamsEnabledWaffleFlag;
},
setsProctoredExamsEnabled: (state, { payload }) => {
state.proctoredExamsEnabledWaffleFlag = payload.proctoredExamsEnabledWaffleFlag;
},
fetchCourseRequest: (state, { payload }) => { fetchCourseRequest: (state, { payload }) => {
state.courseId = payload.courseId; state.courseId = payload.courseId;
state.courseStatus = LOADING; state.courseStatus = LOADING;
@@ -47,6 +55,8 @@ const slice = createSlice({
}); });
export const { export const {
setsSpecialExamsEnabled,
setsProctoredExamsEnabled,
fetchCourseRequest, fetchCourseRequest,
fetchCourseSuccess, fetchCourseSuccess,
fetchCourseFailure, fetchCourseFailure,

View File

@@ -5,11 +5,14 @@ import {
getCourseMetadata, getCourseMetadata,
getCourseBlocks, getCourseBlocks,
getSequenceMetadata, getSequenceMetadata,
postIntegritySignature,
} from './api'; } from './api';
import { import {
updateModel, addModel, updateModelsMap, addModelsMap, updateModels, updateModel, addModel, updateModelsMap, addModelsMap, updateModels,
} from '../../generic/model-store'; } from '../../generic/model-store';
import { import {
setsSpecialExamsEnabled,
setsProctoredExamsEnabled,
fetchCourseRequest, fetchCourseRequest,
fetchCourseSuccess, fetchCourseSuccess,
fetchCourseFailure, fetchCourseFailure,
@@ -31,6 +34,12 @@ export function fetchCourse(courseId) {
modelType: 'coursewareMeta', modelType: 'coursewareMeta',
model: courseMetadataResult.value, model: courseMetadataResult.value,
})); }));
dispatch(setsSpecialExamsEnabled({
specialExamsEnabledWaffleFlag: courseMetadataResult.value.specialExamsEnabledWaffleFlag,
}));
dispatch(setsProctoredExamsEnabled({
proctoredExamsEnabledWaffleFlag: courseMetadataResult.value.proctoredExamsEnabledWaffleFlag,
}));
} }
if (courseBlocksResult.status === 'fulfilled') { if (courseBlocksResult.status === 'fulfilled') {
@@ -177,3 +186,20 @@ export function saveSequencePosition(courseId, sequenceId, activeUnitIndex) {
} }
}; };
} }
export function saveIntegritySignature(courseId) {
return async (dispatch) => {
try {
await postIntegritySignature(courseId);
dispatch(updateModel({
modelType: 'coursewareMeta',
model: {
id: courseId,
userNeedsIntegritySignature: false,
},
}));
} catch (error) {
logError(error);
}
};
}

View File

@@ -53,7 +53,7 @@ function FormattedPricing(props) {
{intl.formatMessage(messages.srPrices, { discountedPrice, originalPrice })} {intl.formatMessage(messages.srPrices, { discountedPrice, originalPrice })}
</span> </span>
<span aria-hidden="true"> <span aria-hidden="true">
<span>{currencySymbol}{discountedPrice}</span> (<del>{currencySymbol}{originalPrice}</del>) <span>{discountedPrice}</span> (<del>{originalPrice}</del>)
</span> </span>
</> </>
); );

View File

@@ -41,6 +41,13 @@ function getAlertIcon(type) {
return faInfoCircle; return faInfoCircle;
} }
function getAlertIconColor(type) {
if (type === ALERT_TYPES.SUCCESS) {
return 'text-success-500';
}
return '';
}
function Alert({ function Alert({
type, dismissible, children, footer, intl, onDismiss, type, dismissible, children, footer, intl, onDismiss,
}) { }) {
@@ -49,7 +56,7 @@ function Alert({
<div className="row w-100 m-0"> <div className="row w-100 m-0">
{type !== ALERT_TYPES.WELCOME && ( {type !== ALERT_TYPES.WELCOME && (
<div className="col-auto p-0 mr-3"> <div className="col-auto p-0 mr-3">
<FontAwesomeIcon icon={getAlertIcon(type)} /> <FontAwesomeIcon icon={getAlertIcon(type)} className={getAlertIconColor(type)} />
</div> </div>
)} )}
<div className="col mr-4 p-0 align-items-start"> <div className="col mr-4 p-0 align-items-start">

View File

@@ -9,6 +9,10 @@
"learning.enrollment.enrollNow.Inline": "التحق الآن", "learning.enrollment.enrollNow.Inline": "التحق الآن",
"learning.enrollment.enrollNow.Sentence": "التحق الآن", "learning.enrollment.enrollNow.Sentence": "التحق الآن",
"learning.enrollment.success": "تم التحاقك في هذا المساق بنجاح!", "learning.enrollment.success": "تم التحاقك في هذا المساق بنجاح!",
"account-activation.alert.title": "Activate your account so you can log back in",
"account-activation.alert.button": "Continue to {siteName}",
"account-activation.alert.message": "We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or\n {sendEmailTag}.",
"account-activation.resend.link": "resend the email",
"learning.logistration.alert": "للاطلاع على محتوى المساق {signIn} أو {register}.", "learning.logistration.alert": "للاطلاع على محتوى المساق {signIn} أو {register}.",
"learning.offer.upgradeNow": "ترقية الآن", "learning.offer.upgradeNow": "ترقية الآن",
"learning.offer.header": "قم بالترقية في {date} ووفر {percentage}% [{fullPricing}]", "learning.offer.header": "قم بالترقية في {date} ووفر {percentage}% [{fullPricing}]",
@@ -39,9 +43,9 @@
"learning.dates.badge.today": "اليوم", "learning.dates.badge.today": "اليوم",
"learning.dates.badge.unreleased": "لم يتم الإصدار بعد", "learning.dates.badge.unreleased": "لم يتم الإصدار بعد",
"learning.dates.badge.verifiedOnly": "موثق فقط", "learning.dates.badge.verifiedOnly": "موثق فقط",
"learning.outline.alert.cert.title": "نحن نعمل على إنشاء الشهادات الخاصة بالمساق.", "learning.outline.alert.cert.when": "This course ended on {courseEndDateFormatted}. Final grades and certificates are\n scheduled to be available after {certificateAvailableDate}.",
"learning.outline.alert.cert.when": "إذا حصلت على شهادة فستتمكن من الوصول إليها في {timeRemaining}. كما يمكنك عرض شهاداتك في ملفك الشخصي {profileLink}.", "cert.alert.earned.unavailable.header": "Your grade and certificate will be ready soon!",
"learning.outline.alert.cert.profile": "الملف الشخصي للمتعلّم", "cert.alert.earned.ready.header": "Congratulations! Your certificate is ready.",
"learning.outline.alert.end.short": "ينتهي هذا المساق في غضون {timeRemaining}في {courseEndTime}.", "learning.outline.alert.end.short": "ينتهي هذا المساق في غضون {timeRemaining}في {courseEndTime}.",
"learning.outline.alert.end.long": "يبدأ المساق في {timeRemaining} بتاريخ {courseStartDate}.", "learning.outline.alert.end.long": "يبدأ المساق في {timeRemaining} بتاريخ {courseStartDate}.",
"learning.outline.alert.start.short": "يبدأ المساق في غضون {timeRemaining} في {courseStartDate}.", "learning.outline.alert.start.short": "يبدأ المساق في غضون {timeRemaining} في {courseStartDate}.",
@@ -53,7 +57,7 @@
"learning.outline.collapseAll": "Collapse all", "learning.outline.collapseAll": "Collapse all",
"learning.outline.completedAssignment": "اكتمل", "learning.outline.completedAssignment": "اكتمل",
"learning.outline.completedSection": "قسم مكتمل", "learning.outline.completedSection": "قسم مكتمل",
"learning.outline.dates": "مواعيد القادمة", "learning.outline.dates": "Important dates",
"learning.outline.editGoal": "تحرير الهدف", "learning.outline.editGoal": "تحرير الهدف",
"learning.outline.expandAll": "Expand all", "learning.outline.expandAll": "Expand all",
"learning.outline.goal": "الهدف", "learning.outline.goal": "الهدف",
@@ -101,24 +105,30 @@
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}", "learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements", "learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
"learning.outline.sequence-due": "{description} في{assignmentDue}", "learning.outline.sequence-due": "{description} في{assignmentDue}",
"learning.outline.alert.upgradecard.verifiedCertLink": "Earn a {verifiedCertLink} of completion to showcase on your resume", "learning.outline.widgets.upgradeCard.verifiedCertLink": "verified certificate",
"learning.outline.alert.upgradecard.nonProfitMission": "Support our {nonProfitMission} at edX", "learning.outline.widgets.upgradeCard.verifiedCertMessage": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"learning.outline.alert.upgradecard.unlock-graded": "Unlock your access to all course activities, including {gradedAssignments}", "learning.outline.widgets.upgradeCard.nonProfitMission": "Support our {nonProfitMission} at edX",
"learning.outline.alert.upgradecard.fullAccess": "{fullAccess} to course content and materials, even after the course ends", "learning.outline.widgets.upgradeCard.gradedAssignments": "graded assignments",
"learning.outline.alert.upgradecard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.", "learning.upgradeCard.verifiedCertLink": "Full access",
"learning.outline.alert.upgradecard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.", "learning.upgradeCard.nonProfitMission": "non-profit mission",
"learning.outline.alert.upgradecard.expiration.days": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left", "learning.outline.widgets.upgradeCard.unlockGraded": "Unlock your access to all course activities, including {gradedAssignments}",
"learning.outline.alert.upgradecard.expiration.hours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left", "learning.outline.widgets.upgradeCard.fullAccess": "{fullAccess} to course content and materials, even after the course ends",
"learning.outline.alert.upgradecard.expiration.minutes": "Less than 1 hour left", "learning.upgradeCard.expirationAccessLoss.progress": "including any progress",
"learning.outline.alert.upgradecard.expirationr": "Course access will expire {date}", "learning.outline.widgets.upgradeCard.expirationVerifiedCert.benefits": "benefits of upgrading",
"learning.outline.alert.upgradecard.code": "Use code {code} at checkout", "learning.outline.widgets.upgradeCard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.",
"learning.outline.alert.upgradecard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount", "learning.outline.widgets.upgradeCard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.",
"learning.outline.alert.upgradecard.accessExpiration": "Upgrade your course today", "learning.outline.widgets.upgradeCard.expirationDays": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left",
"learning.outline.alert.upgradecard.accessExpirationUrgent": "Course Access Expiration", "learning.outline.widgets.upgradeCard.expirationHours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left",
"learning.outline.alert.upgradecard.pursueAverifiedCertificate": "Pursue a verified certificate", "learning.outline.widgets.upgradeCard.expirationMinutes": "Less than 1 hour left",
"learning.outline.widgets.upgradeCard.expiration": "Course access will expire {date}",
"learning.outline.widgets.upgradeCard.code": "Use code {code} at checkout",
"learning.outline.widgets.upgradeCard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount",
"learning.outline.widgets.upgradeCard.accessExpiration": "Upgrade your course today",
"learning.outline.widgets.upgradeCard.accessExpirationUrgent": "Course Access Expiration",
"learning.outline.widgets.upgradeCard.pursueAverifiedCertificate": "Pursue a verified certificate",
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.", "progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.", "progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.",
"courseCelebration.certificateBody.notAvailable.endDate": "بعد انتهاء هذا المساق رسميًا في {endDate}، ستحصل على \nإشعار عبر البريد الإلكتروني بشهادتك. بعد أن تحصل على الشهادة، تأكد من\nعرض إنجازاتك على LinkedIn أو سيرتك الذاتية.", "courseCelebration.certificateBody.notAvailable.endDate": "This course ended on {endDate} and final grades and certificates are scheduled to be\n available after {certAvailableDate}.",
"progress.certificateStatus.notPassingHeader": "Certificate status", "progress.certificateStatus.notPassingHeader": "Certificate status",
"progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.", "progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.",
"progress.certificateStatus.inProgressHeader": "More content is coming soon!", "progress.certificateStatus.inProgressHeader": "More content is coming soon!",
@@ -147,7 +157,7 @@
"progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.", "progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.",
"progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.", "progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.",
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.", "progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
"progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score} other{# {assignmentType} scores}} will be dropped.", "progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped.",
"progress.assignmentType": "Assignment type", "progress.assignmentType": "Assignment type",
"progress.footnotes.backToContent": "Back to content", "progress.footnotes.backToContent": "Back to content",
"progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.", "progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.",
@@ -157,7 +167,8 @@
"progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)", "progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)",
"progress.courseGrade.preview.header": "locked feature", "progress.courseGrade.preview.header": "locked feature",
"progress.courseGrade.preview.header.ariaHidden": "Preview of a ", "progress.courseGrade.preview.header.ariaHidden": "Preview of a ",
"progress.courseGrade.preview.body": "Unlock to view grades and work towards a certificate", "progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
"progress.courseGrade.preview.button.upgrade": "Upgrade now", "progress.courseGrade.preview.button.upgrade": "Upgrade now",
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:", "progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
"progress.courseOutline": "Course Outline", "progress.courseOutline": "Course Outline",
@@ -165,11 +176,12 @@
"progress.detailedGrades": "Detailed grades", "progress.detailedGrades": "Detailed grades",
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.", "progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
"progress.footnotes.title": "Grade summary footnotes", "progress.footnotes.title": "Grade summary footnotes",
"progress.gradeSummary.grade": "Grade",
"progress.courseGrade.grades": "Grades", "progress.courseGrade.grades": "Grades",
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip", "progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
"progress.gradeSummary": "Grade summary", "progress.gradeSummary": "Grade summary",
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip", "progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your score by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.", "progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
"progress.courseGrade.label.passingGrade": "Passing grade", "progress.courseGrade.label.passingGrade": "Passing grade",
"progress.score": "Score", "progress.score": "Score",
"progress.weight": "Weight", "progress.weight": "Weight",
@@ -229,7 +241,6 @@
"notes.button.hide": "إخفاء الملاحظات", "notes.button.hide": "إخفاء الملاحظات",
"courseExit.catalogSearchSuggestion": "هل تطمح إلى تعلّم المزيد؟{searchOurCatalogLink} لاستكشاف المزيد من المساقات والبرامج.", "courseExit.catalogSearchSuggestion": "هل تطمح إلى تعلّم المزيد؟{searchOurCatalogLink} لاستكشاف المزيد من المساقات والبرامج.",
"courseCelebration.certificateBody.available": "اعرض إنجازاتك على لينكد إن أو سيرتك الذاتية اليوم.\nيمكنك تنزيل الشهادة الآن والوصول إليها في أي وقت من\n{dashboardLink} و{profileLink}.", "courseCelebration.certificateBody.available": "اعرض إنجازاتك على لينكد إن أو سيرتك الذاتية اليوم.\nيمكنك تنزيل الشهادة الآن والوصول إليها في أي وقت من\n{dashboardLink} و{profileLink}.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "ستكون قادرًا على الوصول لشهادتك في أي وقت على \n {dashboardLink} و {profileLink}.",
"courseCelebration.certificateBody.unverified": "لإنشاء شهادة يجب عليك إتمام عملية التحقق من الهوية.\n{idVerificationSupportLink} الآن.", "courseCelebration.certificateBody.unverified": "لإنشاء شهادة يجب عليك إتمام عملية التحقق من الهوية.\n{idVerificationSupportLink} الآن.",
"courseCelebration.certificateBody.upgradable": "لم يفت الأوان للترقية. بالنسبة لـ {price} ستقوم بإلغاء تأمين الوصول إلى كافة أنواع \nالواجبات في هذا المساق. عند الانتهاء، ستحصل على شهادة تم التحقق منها وهي إحدى\nالوثائق القيّمة لتحسين فرصك الوظيفية وتطويرك المهني، أو لتسليط الضوء على\nشهادة في التطبيقات التعليمية.", "courseCelebration.certificateBody.upgradable": "لم يفت الأوان للترقية. بالنسبة لـ {price} ستقوم بإلغاء تأمين الوصول إلى كافة أنواع \nالواجبات في هذا المساق. عند الانتهاء، ستحصل على شهادة تم التحقق منها وهي إحدى\nالوثائق القيّمة لتحسين فرصك الوظيفية وتطويرك المهني، أو لتسليط الضوء على\nشهادة في التطبيقات التعليمية.",
"courseCelebration.upgradeDiscountCodePrompt": "استخدم الرمز {code} عند إتمام الطلب لخصم {percent}%!", "courseCelebration.upgradeDiscountCodePrompt": "استخدم الرمز {code} عند إتمام الطلب لخصم {percent}%!",
@@ -242,7 +253,8 @@
"courseCelebration.dashboardInfo": "يمكنك الوصول إلى هذا المساق ومواده على لوحة معلوماتك {dashboardLink}.", "courseCelebration.dashboardInfo": "يمكنك الوصول إلى هذا المساق ومواده على لوحة معلوماتك {dashboardLink}.",
"courseExit.programs.applyForCredit": "تقدم بطلب للحصول على ائتمان", "courseExit.programs.applyForCredit": "تقدم بطلب للحصول على ائتمان",
"courseCelebration.certificateHeader.downloadable": "!شهادتك جاهزة", "courseCelebration.certificateHeader.downloadable": "!شهادتك جاهزة",
"courseCelebration.certificateHeader.notAvailable": "ستكون شهادتك متاحة قريبًا!", "courseCelebration.certificateHeader.notAvailable": "Your grade and certificate will be ready soon!",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
"courseCelebration.certificateHeader.unverified": "يجب إكمال عملية التحقق للحصول على شهادتك", "courseCelebration.certificateHeader.unverified": "يجب إكمال عملية التحقق للحصول على شهادتك",
"courseCelebration.certificateHeader.requestable": "تهانينا، لقد تأهلت للحصول على شهادة!", "courseCelebration.certificateHeader.requestable": "تهانينا، لقد تأهلت للحصول على شهادة!",
"courseCelebration.certificateHeader.upgradable": "قم بالترقية للحصول على شهادة معتمدة", "courseCelebration.certificateHeader.upgradable": "قم بالترقية للحصول على شهادة معتمدة",
@@ -300,20 +312,24 @@
"learn.contentLock.content.locked": "محتوى مغلق", "learn.contentLock.content.locked": "محتوى مغلق",
"learn.contentLock.complete.prerequisite": "يجب استيفاء المتطلبات الأساسية: '{priceqSectionName}' للوصول إلى هذا المحتوى.", "learn.contentLock.complete.prerequisite": "يجب استيفاء المتطلبات الأساسية: '{priceqSectionName}' للوصول إلى هذا المحتوى.",
"learn.contentLock.goToSection": "انتقل إلى قسم المتطلبات الأساسية", "learn.contentLock.goToSection": "انتقل إلى قسم المتطلبات الأساسية",
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
"learn.honorCode.name": "Honor Code",
"learn.honorCode.cancel": "Cancel",
"learn.honorCode.agree": "I agree",
"gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume", "gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}", "gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}",
"gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends", "gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends",
"gatedContent.paragraph.bulletFour": "Support our {nonProfit} mission at edX", "gatedContent.paragraph.bulletFour": "Support our {nonProfitMission} at edX",
"learn.lockPaywall.title": "Graded assignments are locked", "learn.lockPaywall.title": "Graded assignments are locked",
"learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.", "learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.",
"learn.lockPaywall.upgrade.link": "Upgrade for {currencySymbol}{price}",
"learn.lockPaywall.example.alt": "عينة الشهادة", "learn.lockPaywall.example.alt": "عينة الشهادة",
"learn.lockPaywall.list.intro": "When you upgrade, you:", "learn.lockPaywall.list.intro": "When you upgrade, you:",
"learn.lockPaywall.list.bullet1.linktext": "verified certificate", "learn.lockPaywall.list.bullet1.linktext": "verified certificate",
"learn.lockPaywall.list.bullet2.boldtext": "graded assignments", "learn.lockPaywall.list.bullet2.boldtext": "graded assignments",
"learn.lockPaywall.list.bullet3.boldtext": "Full access", "learn.lockPaywall.list.bullet3.boldtext": "Full access",
"learn.lockPaywall.list.bullet4.boldtext": "non-profit", "learn.lockPaywall.list.bullet4.boldtext": "non-profit mission",
"learn.loading.content.lock": "جارٍ تحميل رسائل المحتوى المغلق...", "learn.loading.content.lock": "جارٍ تحميل رسائل المحتوى المغلق...",
"learn.loading.honor.codk": "Loading honor code messaging...",
"learn.loading.learning.sequence": "جارِ تحميل سلسلة التعلّم...", "learn.loading.learning.sequence": "جارِ تحميل سلسلة التعلّم...",
"learn.course.load.failure": "حدث خطأ أثناء تحميل هذا المساق.", "learn.course.load.failure": "حدث خطأ أثناء تحميل هذا المساق.",
"learn.sequence.no.content": "لا يوجد محتوى هنا.", "learn.sequence.no.content": "لا يوجد محتوى هنا.",
@@ -350,17 +366,22 @@
"learning.offer.screenReaderPrices": "السعر الأصلي: {originalPrice}, سعر الخصم: {discountedPrice}", "learning.offer.screenReaderPrices": "السعر الأصلي: {originalPrice}, سعر الخصم: {discountedPrice}",
"learning.upgradeButton.screenReaderInlinePrices": "السعر الأصلي: {originalPrice}", "learning.upgradeButton.screenReaderInlinePrices": "السعر الأصلي: {originalPrice}",
"learning.upgradeButton.buttonText": "Upgrade for {pricing}", "learning.upgradeButton.buttonText": "Upgrade for {pricing}",
"learning.upgradeNowButton.buttonText": "Upgrade now for {pricing}",
"masquerade-widget.userName.error.generic": "حدث خطأ؛ يرجى المحاولة مرة أخرى.", "masquerade-widget.userName.error.generic": "حدث خطأ؛ يرجى المحاولة مرة أخرى.",
"masquerade-widget.userName.input.placeholder": "اسم المستخدم أو البريد الإلكتروني", "masquerade-widget.userName.input.placeholder": "اسم المستخدم أو البريد الإلكتروني",
"masquerade-widget.userName.input.label": "عرف كهذا المستخدم", "masquerade-widget.userName.input.label": "عرف كهذا المستخدم",
"learning.streakCelebration.header": "day streak", "learning.streakCelebration.congratulations": "Congratulations!",
"learning.streakCelebration.body": "Keep it up, youre on a roll!", "learning.streakCelebration.body": "Keep it up, youre on a roll!",
"learning.streakCelebration.button": "Keep it up", "learning.streakCelebration.button": "Keep it up",
"learning.streakCelebration.buttonSrOnly": "Close modal and continue", "learning.streakCelebration.buttonSrOnly": "Close modal and continue",
"learning.streakCelebration.buttonAA759": "Continue with course",
"learning.streakCelebration.header": "day streak",
"learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course", "learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course",
"learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average", "learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average",
"learning.streakCelebration.streakDiscountMessage": "Youve unlocked a 15% off discount when you upgrade this course for a limited time only.",
"learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.", "learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.",
"learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.", "learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.",
"learning.streakCelebration.streakAA759EndDateMessage": "Ends {date}.",
"learning.loading.failure": "حدث خطأ أثناء تحميل هذا المساق.", "learning.loading.failure": "حدث خطأ أثناء تحميل هذا المساق.",
"learning.loading": "يتم الآن تحميل صفحة المساق..." "learning.loading": "يتم الآن تحميل صفحة المساق..."
} }

View File

@@ -9,6 +9,10 @@
"learning.enrollment.enrollNow.Inline": "Enroll now", "learning.enrollment.enrollNow.Inline": "Enroll now",
"learning.enrollment.enrollNow.Sentence": "Inscríbete ahora.", "learning.enrollment.enrollNow.Sentence": "Inscríbete ahora.",
"learning.enrollment.success": "¡Te has inscrito exitosamente en este curso!", "learning.enrollment.success": "¡Te has inscrito exitosamente en este curso!",
"account-activation.alert.title": "Activate your account so you can log back in",
"account-activation.alert.button": "Continue to {siteName}",
"account-activation.alert.message": "We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or\n {sendEmailTag}.",
"account-activation.resend.link": "resend the email",
"learning.logistration.alert": "Para ver el contenido del curso, {signIn} o {register}.", "learning.logistration.alert": "Para ver el contenido del curso, {signIn} o {register}.",
"learning.offer.upgradeNow": "Upgrade now", "learning.offer.upgradeNow": "Upgrade now",
"learning.offer.header": "Mejora de categoría antes del {date} y ahorra el {percentage} % de [{fullPricing}]", "learning.offer.header": "Mejora de categoría antes del {date} y ahorra el {percentage} % de [{fullPricing}]",
@@ -39,9 +43,9 @@
"learning.dates.badge.today": "Hoy", "learning.dates.badge.today": "Hoy",
"learning.dates.badge.unreleased": "Not yet released", "learning.dates.badge.unreleased": "Not yet released",
"learning.dates.badge.verifiedOnly": "Solo verificado", "learning.dates.badge.verifiedOnly": "Solo verificado",
"learning.outline.alert.cert.title": "We are working on generating course certificates.", "learning.outline.alert.cert.when": "This course ended on {courseEndDateFormatted}. Final grades and certificates are\n scheduled to be available after {certificateAvailableDate}.",
"learning.outline.alert.cert.when": "Si ha obtenido un certificado, podrá acceder al mismo en {timeRemaining}. También podrás ver tus certificados en tu {profileLink}.", "cert.alert.earned.unavailable.header": "Your grade and certificate will be ready soon!",
"learning.outline.alert.cert.profile": "Learner Profile", "cert.alert.earned.ready.header": "Congratulations! Your certificate is ready.",
"learning.outline.alert.end.short": "Este curso acaba en {timeRemaining} a la/s {courseEndTime}.", "learning.outline.alert.end.short": "Este curso acaba en {timeRemaining} a la/s {courseEndTime}.",
"learning.outline.alert.end.long": "El curso comienza en {timeRemaining} el {courseStartDate}.", "learning.outline.alert.end.long": "El curso comienza en {timeRemaining} el {courseStartDate}.",
"learning.outline.alert.start.short": "El curso comienza en {timeRemaining} a la/s {courseStartTime}.", "learning.outline.alert.start.short": "El curso comienza en {timeRemaining} a la/s {courseStartTime}.",
@@ -53,7 +57,7 @@
"learning.outline.collapseAll": "Colapsar todo", "learning.outline.collapseAll": "Colapsar todo",
"learning.outline.completedAssignment": "Completed", "learning.outline.completedAssignment": "Completed",
"learning.outline.completedSection": "Sección completada", "learning.outline.completedSection": "Sección completada",
"learning.outline.dates": "Próximas fechas", "learning.outline.dates": "Important dates",
"learning.outline.editGoal": "Editar objetivo", "learning.outline.editGoal": "Editar objetivo",
"learning.outline.expandAll": "Expandir todo", "learning.outline.expandAll": "Expandir todo",
"learning.outline.goal": "Objetivo", "learning.outline.goal": "Objetivo",
@@ -101,24 +105,30 @@
"learning.proctoringPanel.onboardingButtonNotOpen": "Apertura de la integración: {releaseDate}", "learning.proctoringPanel.onboardingButtonNotOpen": "Apertura de la integración: {releaseDate}",
"learning.proctoringPanel.reviewRequirementsButton": "Revisar las instrucciones y los requisitos del sistema", "learning.proctoringPanel.reviewRequirementsButton": "Revisar las instrucciones y los requisitos del sistema",
"learning.outline.sequence-due": "Fecha límite para {description}: {assignmentDue}", "learning.outline.sequence-due": "Fecha límite para {description}: {assignmentDue}",
"learning.outline.alert.upgradecard.verifiedCertLink": "Earn a {verifiedCertLink} of completion to showcase on your resume", "learning.outline.widgets.upgradeCard.verifiedCertLink": "certificado verificado",
"learning.outline.alert.upgradecard.nonProfitMission": "Support our {nonProfitMission} at edX", "learning.outline.widgets.upgradeCard.verifiedCertMessage": "Obtén un {verifiedCertLink} de finalización para compartirlo en tu currículum",
"learning.outline.alert.upgradecard.unlock-graded": "Unlock your access to all course activities, including {gradedAssignments}", "learning.outline.widgets.upgradeCard.nonProfitMission": "Apoya nuestra {nonProfitMission} en edX",
"learning.outline.alert.upgradecard.fullAccess": "{fullAccess} al contenido y los materiales del curso, incluso después de que finalice el curso", "learning.outline.widgets.upgradeCard.gradedAssignments": "tareas calificadas",
"learning.outline.alert.upgradecard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.", "learning.upgradeCard.verifiedCertLink": "Acceso completo",
"learning.outline.alert.upgradecard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.", "learning.upgradeCard.nonProfitMission": "misión sin fines de lucro",
"learning.outline.alert.upgradecard.expiration.days": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left", "learning.outline.widgets.upgradeCard.unlockGraded": "Desbloquea el acceso a todas las actividades del curso, incluidas las {gradedAssignments}",
"learning.outline.alert.upgradecard.expiration.hours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left", "learning.outline.widgets.upgradeCard.fullAccess": "{fullAccess} al contenido y los materiales del curso, incluso después de que finalice el curso",
"learning.outline.alert.upgradecard.expiration.minutes": "Queda menos de 1 hora", "learning.upgradeCard.expirationAccessLoss.progress": "incluido cualquier progreso",
"learning.outline.alert.upgradecard.expirationr": "Course access will expire {date}", "learning.outline.widgets.upgradeCard.expirationVerifiedCert.benefits": "beneficios del cambio",
"learning.outline.alert.upgradecard.code": "Usa el código {code} al finalizar la compra", "learning.outline.widgets.upgradeCard.expirationAccessLoss": "Perderás todo el acceso a este curso, {includingAnyProgress}, el {date}.",
"learning.outline.alert.upgradecard.firstTimeLearnerDiscount": "{percentage}% de descuento de bienvenida para estudiantes nuevos", "learning.outline.widgets.upgradeCard.expirationVerifiedCert": "Cambiarte a la opción verificada permite obtener un certificado verificado y obtener acceso a numerosas funciones. Obtén más información sobre los {benefitsOfUpgrading}.",
"learning.outline.alert.upgradecard.accessExpiration": "Cámbiate a la opción verificada", "learning.outline.widgets.upgradeCard.expirationDays": "Quedan {dayCount, number} {dayCount, plural, \none {day}\nother {days}}",
"learning.outline.alert.upgradecard.accessExpirationUrgent": "Vencimiento del acceso al curso", "learning.outline.widgets.upgradeCard.expirationHours": "Quedan {hourCount, number} {hourCount, plural,\none {hour}\nother {hours}}",
"learning.outline.alert.upgradecard.pursueAverifiedCertificate": "Pursue a verified certificate", "learning.outline.widgets.upgradeCard.expirationMinutes": "Queda menos de 1 hora",
"learning.outline.widgets.upgradeCard.expiration": "El acceso al curso expirará {date}",
"learning.outline.widgets.upgradeCard.code": "Usa el código {code} al finalizar la compra",
"learning.outline.widgets.upgradeCard.firstTimeLearnerDiscount": "{percentage}% de descuento de bienvenida para estudiantes nuevos",
"learning.outline.widgets.upgradeCard.accessExpiration": "Cámbiate a la opción verificada",
"learning.outline.widgets.upgradeCard.accessExpirationUrgent": "Vencimiento del acceso al curso",
"learning.outline.widgets.upgradeCard.pursueAverifiedCertificate": "Pursue a verified certificate",
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.", "progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.", "progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.",
"courseCelebration.certificateBody.notAvailable.endDate": "Después de que este curso termine oficialmente el {endDate}, recibirás una\n notificación por correo electrónico con tu certificado. Una vez que tengas tu certificado, asegúrate\n de mostrar tu logro en LinkedIn o en tu currículum.", "courseCelebration.certificateBody.notAvailable.endDate": "This course ended on {endDate} and final grades and certificates are scheduled to be\n available after {certAvailableDate}.",
"progress.certificateStatus.notPassingHeader": "Certificate status", "progress.certificateStatus.notPassingHeader": "Certificate status",
"progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.", "progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.",
"progress.certificateStatus.inProgressHeader": "More content is coming soon!", "progress.certificateStatus.inProgressHeader": "More content is coming soon!",
@@ -147,7 +157,7 @@
"progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.", "progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.",
"progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.", "progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.",
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.", "progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
"progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score} other{# {assignmentType} scores}} will be dropped.", "progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped.",
"progress.assignmentType": "Assignment type", "progress.assignmentType": "Assignment type",
"progress.footnotes.backToContent": "Back to content", "progress.footnotes.backToContent": "Back to content",
"progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.", "progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.",
@@ -157,7 +167,8 @@
"progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)", "progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)",
"progress.courseGrade.preview.header": "locked feature", "progress.courseGrade.preview.header": "locked feature",
"progress.courseGrade.preview.header.ariaHidden": "Preview of a ", "progress.courseGrade.preview.header.ariaHidden": "Preview of a ",
"progress.courseGrade.preview.body": "Unlock to view grades and work towards a certificate", "progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
"progress.courseGrade.preview.button.upgrade": "Upgrade now", "progress.courseGrade.preview.button.upgrade": "Upgrade now",
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:", "progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
"progress.courseOutline": "Course Outline", "progress.courseOutline": "Course Outline",
@@ -165,11 +176,12 @@
"progress.detailedGrades": "Detailed grades", "progress.detailedGrades": "Detailed grades",
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.", "progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
"progress.footnotes.title": "Grade summary footnotes", "progress.footnotes.title": "Grade summary footnotes",
"progress.gradeSummary.grade": "Grade",
"progress.courseGrade.grades": "Grades", "progress.courseGrade.grades": "Grades",
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip", "progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
"progress.gradeSummary": "Grade summary", "progress.gradeSummary": "Grade summary",
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip", "progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your score by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.", "progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
"progress.courseGrade.label.passingGrade": "Passing grade", "progress.courseGrade.label.passingGrade": "Passing grade",
"progress.score": "Score", "progress.score": "Score",
"progress.weight": "Weight", "progress.weight": "Weight",
@@ -229,7 +241,6 @@
"notes.button.hide": "Ocultar Notas", "notes.button.hide": "Ocultar Notas",
"courseExit.catalogSearchSuggestion": "¿Quieres saber más? {searchOurCatalogLink} para buscar más cursos y programas por explorar.", "courseExit.catalogSearchSuggestion": "¿Quieres saber más? {searchOurCatalogLink} para buscar más cursos y programas por explorar.",
"courseCelebration.certificateBody.available": "\n Muestra tu logro en LinkedIn o en tu currículum hoy mismo.\n Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu\n {dashboardLink} y {profileLink}.", "courseCelebration.certificateBody.available": "\n Muestra tu logro en LinkedIn o en tu currículum hoy mismo.\n Puedes descargar tu certificado ahora y acceder a él en cualquier momento desde tu\n {dashboardLink} y {profileLink}.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "Podrás acceder a tu certificado en cualquier momento desde tu\n {dashboardLink} y {profileLink}.",
"courseCelebration.certificateBody.unverified": "Para generar un certificado, debes completar la verificación de ID.\n {idVerificationSupportLink} ahora.", "courseCelebration.certificateBody.unverified": "Para generar un certificado, debes completar la verificación de ID.\n {idVerificationSupportLink} ahora.",
"courseCelebration.certificateBody.upgradable": "No es demasiado tarde para mejorar de categoría. Por {price}, obtendrás acceso a todas las asignaciones\n calificadas de este curso. Al terminar, recibirás un certificado verificado que es una\n valiosa credencial para mejorar tus perspectivas de trabajo y avanzar en tu carrera, o puedes usar dicho\n certificado para destacarlo en solicitudes universitarias.", "courseCelebration.certificateBody.upgradable": "No es demasiado tarde para mejorar de categoría. Por {price}, obtendrás acceso a todas las asignaciones\n calificadas de este curso. Al terminar, recibirás un certificado verificado que es una\n valiosa credencial para mejorar tus perspectivas de trabajo y avanzar en tu carrera, o puedes usar dicho\n certificado para destacarlo en solicitudes universitarias.",
"courseCelebration.upgradeDiscountCodePrompt": "Utiliza el código {code} en el momento de la compra para obtener un {percent} % de descuento.", "courseCelebration.upgradeDiscountCodePrompt": "Utiliza el código {code} en el momento de la compra para obtener un {percent} % de descuento.",
@@ -242,7 +253,8 @@
"courseCelebration.dashboardInfo": "Puedes acceder a este curso y a sus materiales en tu {dashboardLink}.", "courseCelebration.dashboardInfo": "Puedes acceder a este curso y a sus materiales en tu {dashboardLink}.",
"courseExit.programs.applyForCredit": "Solicitar crédito", "courseExit.programs.applyForCredit": "Solicitar crédito",
"courseCelebration.certificateHeader.downloadable": "¡Tu certificado está disponible!", "courseCelebration.certificateHeader.downloadable": "¡Tu certificado está disponible!",
"courseCelebration.certificateHeader.notAvailable": "Your certificate will be available soon!", "courseCelebration.certificateHeader.notAvailable": "Your grade and certificate will be ready soon!",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
"courseCelebration.certificateHeader.unverified": "Debes completar la verificación para recibir tu certificado.", "courseCelebration.certificateHeader.unverified": "Debes completar la verificación para recibir tu certificado.",
"courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!", "courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!",
"courseCelebration.certificateHeader.upgradable": "Mejora de categoría para obtener un certificado verificado", "courseCelebration.certificateHeader.upgradable": "Mejora de categoría para obtener un certificado verificado",
@@ -300,13 +312,16 @@
"learn.contentLock.content.locked": "Contenido Bloqueado", "learn.contentLock.content.locked": "Contenido Bloqueado",
"learn.contentLock.complete.prerequisite": "Debe completar el prerrequisito: '{prereqSectionName}'para acceder a este contenido.", "learn.contentLock.complete.prerequisite": "Debe completar el prerrequisito: '{prereqSectionName}'para acceder a este contenido.",
"learn.contentLock.goToSection": "Ir a la Sección de Prerrequisitos", "learn.contentLock.goToSection": "Ir a la Sección de Prerrequisitos",
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
"learn.honorCode.name": "Honor Code",
"learn.honorCode.cancel": "Cancel",
"learn.honorCode.agree": "I agree",
"gatedContent.paragraph.bulletOne": "Obtén un {verifiedCertLink} de finalización para compartirlo en tu currículum", "gatedContent.paragraph.bulletOne": "Obtén un {verifiedCertLink} de finalización para compartirlo en tu currículum",
"gatedContent.paragraph.bulletTwo": "Desbloquea el acceso a todas las actividades del curso, incluidas las {gradedAssignments}", "gatedContent.paragraph.bulletTwo": "Desbloquea el acceso a todas las actividades del curso, incluidas las {gradedAssignments}",
"gatedContent.paragraph.bulletThree": "{fullAccess} al contenido y los materiales del curso, incluso después de que finalice el curso", "gatedContent.paragraph.bulletThree": "{fullAccess} al contenido y los materiales del curso, incluso después de que finalice el curso",
"gatedContent.paragraph.bulletFour": "Apoya nuestra {nonProfit} en edX", "gatedContent.paragraph.bulletFour": "Apoya nuestra {nonProfitMission} en edX",
"learn.lockPaywall.title": "Las tareas calificadas están bloqueadas", "learn.lockPaywall.title": "Las tareas calificadas están bloqueadas",
"learn.lockPaywall.content": "Cámbiate a la opción verificada para obtener acceso a funciones bloqueadas como esta y aprovechar al máximo tu curso.", "learn.lockPaywall.content": "Cámbiate a la opción verificada para obtener acceso a funciones bloqueadas como esta y aprovechar al máximo tu curso.",
"learn.lockPaywall.upgrade.link": "Opción verificada {currencySymbol}{price}",
"learn.lockPaywall.example.alt": "Certificado de ejemplo", "learn.lockPaywall.example.alt": "Certificado de ejemplo",
"learn.lockPaywall.list.intro": "Cuando te cambias a la opción verificada, tú:", "learn.lockPaywall.list.intro": "Cuando te cambias a la opción verificada, tú:",
"learn.lockPaywall.list.bullet1.linktext": "certificado verificado", "learn.lockPaywall.list.bullet1.linktext": "certificado verificado",
@@ -314,6 +329,7 @@
"learn.lockPaywall.list.bullet3.boldtext": "Acceso completo", "learn.lockPaywall.list.bullet3.boldtext": "Acceso completo",
"learn.lockPaywall.list.bullet4.boldtext": "misión sin fines de lucro", "learn.lockPaywall.list.bullet4.boldtext": "misión sin fines de lucro",
"learn.loading.content.lock": "Cargando la mensajería de contenido bloqueado...", "learn.loading.content.lock": "Cargando la mensajería de contenido bloqueado...",
"learn.loading.honor.codk": "Loading honor code messaging...",
"learn.loading.learning.sequence": "Cargando la secuencia de aprendizaje...", "learn.loading.learning.sequence": "Cargando la secuencia de aprendizaje...",
"learn.course.load.failure": "Hubo un error al cargar este curso.", "learn.course.load.failure": "Hubo un error al cargar este curso.",
"learn.sequence.no.content": "Aquí no hay contenido.", "learn.sequence.no.content": "Aquí no hay contenido.",
@@ -350,17 +366,22 @@
"learning.offer.screenReaderPrices": "Precio original: {originalPrice}; precio con descuento: {discountedPrice}", "learning.offer.screenReaderPrices": "Precio original: {originalPrice}; precio con descuento: {discountedPrice}",
"learning.upgradeButton.screenReaderInlinePrices": "Precio original: {originalPrice}", "learning.upgradeButton.screenReaderInlinePrices": "Precio original: {originalPrice}",
"learning.upgradeButton.buttonText": "Upgrade for {pricing}", "learning.upgradeButton.buttonText": "Upgrade for {pricing}",
"learning.upgradeNowButton.buttonText": "Cambia a la opción paga por {pricing}",
"masquerade-widget.userName.error.generic": "Se ha producido un error. Inténtalo de nuevo.", "masquerade-widget.userName.error.generic": "Se ha producido un error. Inténtalo de nuevo.",
"masquerade-widget.userName.input.placeholder": "Nombre de usuario o correo electrónico", "masquerade-widget.userName.input.placeholder": "Nombre de usuario o correo electrónico",
"masquerade-widget.userName.input.label": "Hazte pasar por este usuario", "masquerade-widget.userName.input.label": "Hazte pasar por este usuario",
"learning.streakCelebration.header": "day streak", "learning.streakCelebration.congratulations": "Congratulations!",
"learning.streakCelebration.body": "Sigue así, ¡estás de buena racha!", "learning.streakCelebration.body": "Sigue así, ¡estás de buena racha!",
"learning.streakCelebration.button": "Sigue así", "learning.streakCelebration.button": "Sigue así",
"learning.streakCelebration.buttonSrOnly": "Cerrar el modal y continuar", "learning.streakCelebration.buttonSrOnly": "Cerrar el modal y continuar",
"learning.streakCelebration.buttonAA759": "Continuar con el curso",
"learning.streakCelebration.header": "day streak",
"learning.streakCelebration.factoidABoldedSection": "tienen 20 veces más probabilidades de aprobar el curso", "learning.streakCelebration.factoidABoldedSection": "tienen 20 veces más probabilidades de aprobar el curso",
"learning.streakCelebration.factoidBBoldedSection": "completan 5 veces la cantidad de contenido del curso en promedio", "learning.streakCelebration.factoidBBoldedSection": "completan 5 veces la cantidad de contenido del curso en promedio",
"learning.streakCelebration.streakDiscountMessage": "Has desbloqueado un descuento del 15% al cambiar a la opción page de este curso sólo por tiempo limitado.",
"learning.streakcelebration.factoida": "Los usuarios que aprenden {streak_length} días seguidos {bolded_section} que los que no lo hacen.", "learning.streakcelebration.factoida": "Los usuarios que aprenden {streak_length} días seguidos {bolded_section} que los que no lo hacen.",
"learning.streakcelebration.factoidb": "Los usuarios que aprenden {streak_length} días seguidos {bolded_section} frente a los que no lo hacen.", "learning.streakcelebration.factoidb": "Los usuarios que aprenden {streak_length} días seguidos {bolded_section} frente a los que no lo hacen.",
"learning.streakCelebration.streakAA759EndDateMessage": "Termina {date}.",
"learning.loading.failure": "Hubo un error al cargar este curso.", "learning.loading.failure": "Hubo un error al cargar este curso.",
"learning.loading": "Cargando la página del curso..." "learning.loading": "Cargando la página del curso..."
} }

View File

@@ -9,6 +9,10 @@
"learning.enrollment.enrollNow.Inline": "Enroll now", "learning.enrollment.enrollNow.Inline": "Enroll now",
"learning.enrollment.enrollNow.Sentence": "Enroll now.", "learning.enrollment.enrollNow.Sentence": "Enroll now.",
"learning.enrollment.success": "You've successfully enrolled in this course!", "learning.enrollment.success": "You've successfully enrolled in this course!",
"account-activation.alert.title": "Activate your account so you can log back in",
"account-activation.alert.button": "Continue to {siteName}",
"account-activation.alert.message": "We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or\n {sendEmailTag}.",
"account-activation.resend.link": "resend the email",
"learning.logistration.alert": "To see course content, {signIn} or {register}.", "learning.logistration.alert": "To see course content, {signIn} or {register}.",
"learning.offer.upgradeNow": "Upgrade now", "learning.offer.upgradeNow": "Upgrade now",
"learning.offer.header": "Upgrade by {date} and save {percentage}% [{fullPricing}]", "learning.offer.header": "Upgrade by {date} and save {percentage}% [{fullPricing}]",
@@ -39,9 +43,9 @@
"learning.dates.badge.today": "Today", "learning.dates.badge.today": "Today",
"learning.dates.badge.unreleased": "Not yet released", "learning.dates.badge.unreleased": "Not yet released",
"learning.dates.badge.verifiedOnly": "Verified only", "learning.dates.badge.verifiedOnly": "Verified only",
"learning.outline.alert.cert.title": "We are working on generating course certificates.", "learning.outline.alert.cert.when": "This course ended on {courseEndDateFormatted}. Final grades and certificates are\n scheduled to be available after {certificateAvailableDate}.",
"learning.outline.alert.cert.when": "If you have earned a certificate, you will be able to access it {timeRemaining}. You will also be able to view your certificates on your {profileLink}.", "cert.alert.earned.unavailable.header": "Your grade and certificate will be ready soon!",
"learning.outline.alert.cert.profile": "Learner Profile", "cert.alert.earned.ready.header": "Congratulations! Your certificate is ready.",
"learning.outline.alert.end.short": "This course is ending {timeRemaining} at {courseEndTime}.", "learning.outline.alert.end.short": "This course is ending {timeRemaining} at {courseEndTime}.",
"learning.outline.alert.end.long": "Course starts {timeRemaining} on {courseStartDate}.", "learning.outline.alert.end.long": "Course starts {timeRemaining} on {courseStartDate}.",
"learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.", "learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.",
@@ -53,7 +57,7 @@
"learning.outline.collapseAll": "Collapse all", "learning.outline.collapseAll": "Collapse all",
"learning.outline.completedAssignment": "Completed", "learning.outline.completedAssignment": "Completed",
"learning.outline.completedSection": "Completed section", "learning.outline.completedSection": "Completed section",
"learning.outline.dates": "Upcoming Dates", "learning.outline.dates": "Important dates",
"learning.outline.editGoal": "Edit goal", "learning.outline.editGoal": "Edit goal",
"learning.outline.expandAll": "Expand all", "learning.outline.expandAll": "Expand all",
"learning.outline.goal": "Goal", "learning.outline.goal": "Goal",
@@ -101,24 +105,30 @@
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}", "learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements", "learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
"learning.outline.sequence-due": "{description} due {assignmentDue}", "learning.outline.sequence-due": "{description} due {assignmentDue}",
"learning.outline.alert.upgradecard.verifiedCertLink": "Earn a {verifiedCertLink} of completion to showcase on your resume", "learning.outline.widgets.upgradeCard.verifiedCertLink": "verified certificate",
"learning.outline.alert.upgradecard.nonProfitMission": "Support our {nonProfitMission} at edX", "learning.outline.widgets.upgradeCard.verifiedCertMessage": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"learning.outline.alert.upgradecard.unlock-graded": "Unlock your access to all course activities, including {gradedAssignments}", "learning.outline.widgets.upgradeCard.nonProfitMission": "Support our {nonProfitMission} at edX",
"learning.outline.alert.upgradecard.fullAccess": "{fullAccess} to course content and materials, even after the course ends", "learning.outline.widgets.upgradeCard.gradedAssignments": "graded assignments",
"learning.outline.alert.upgradecard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.", "learning.upgradeCard.verifiedCertLink": "Full access",
"learning.outline.alert.upgradecard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.", "learning.upgradeCard.nonProfitMission": "non-profit mission",
"learning.outline.alert.upgradecard.expiration.days": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left", "learning.outline.widgets.upgradeCard.unlockGraded": "Unlock your access to all course activities, including {gradedAssignments}",
"learning.outline.alert.upgradecard.expiration.hours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left", "learning.outline.widgets.upgradeCard.fullAccess": "{fullAccess} to course content and materials, even after the course ends",
"learning.outline.alert.upgradecard.expiration.minutes": "Less than 1 hour left", "learning.upgradeCard.expirationAccessLoss.progress": "including any progress",
"learning.outline.alert.upgradecard.expirationr": "Course access will expire {date}", "learning.outline.widgets.upgradeCard.expirationVerifiedCert.benefits": "benefits of upgrading",
"learning.outline.alert.upgradecard.code": "Use code {code} at checkout", "learning.outline.widgets.upgradeCard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.",
"learning.outline.alert.upgradecard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount", "learning.outline.widgets.upgradeCard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.",
"learning.outline.alert.upgradecard.accessExpiration": "Upgrade your course today", "learning.outline.widgets.upgradeCard.expirationDays": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left",
"learning.outline.alert.upgradecard.accessExpirationUrgent": "Course Access Expiration", "learning.outline.widgets.upgradeCard.expirationHours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left",
"learning.outline.alert.upgradecard.pursueAverifiedCertificate": "Pursue a verified certificate", "learning.outline.widgets.upgradeCard.expirationMinutes": "Less than 1 hour left",
"learning.outline.widgets.upgradeCard.expiration": "Course access will expire {date}",
"learning.outline.widgets.upgradeCard.code": "Use code {code} at checkout",
"learning.outline.widgets.upgradeCard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount",
"learning.outline.widgets.upgradeCard.accessExpiration": "Upgrade your course today",
"learning.outline.widgets.upgradeCard.accessExpirationUrgent": "Course Access Expiration",
"learning.outline.widgets.upgradeCard.pursueAverifiedCertificate": "Pursue a verified certificate",
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.", "progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.", "progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.",
"courseCelebration.certificateBody.notAvailable.endDate": "After this course officially ends on {endDate}, you will receive an\n email notification with your certificate. Once you have your certificate, be sure\n to showcase your accomplishment on LinkedIn or your resumé.", "courseCelebration.certificateBody.notAvailable.endDate": "This course ended on {endDate} and final grades and certificates are scheduled to be\n available after {certAvailableDate}.",
"progress.certificateStatus.notPassingHeader": "Certificate status", "progress.certificateStatus.notPassingHeader": "Certificate status",
"progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.", "progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.",
"progress.certificateStatus.inProgressHeader": "More content is coming soon!", "progress.certificateStatus.inProgressHeader": "More content is coming soon!",
@@ -147,7 +157,7 @@
"progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.", "progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.",
"progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.", "progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.",
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.", "progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
"progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score} other{# {assignmentType} scores}} will be dropped.", "progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped.",
"progress.assignmentType": "Assignment type", "progress.assignmentType": "Assignment type",
"progress.footnotes.backToContent": "Back to content", "progress.footnotes.backToContent": "Back to content",
"progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.", "progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.",
@@ -157,7 +167,8 @@
"progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)", "progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)",
"progress.courseGrade.preview.header": "locked feature", "progress.courseGrade.preview.header": "locked feature",
"progress.courseGrade.preview.header.ariaHidden": "Preview of a ", "progress.courseGrade.preview.header.ariaHidden": "Preview of a ",
"progress.courseGrade.preview.body": "Unlock to view grades and work towards a certificate", "progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
"progress.courseGrade.preview.button.upgrade": "Upgrade now", "progress.courseGrade.preview.button.upgrade": "Upgrade now",
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:", "progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
"progress.courseOutline": "Course Outline", "progress.courseOutline": "Course Outline",
@@ -165,11 +176,12 @@
"progress.detailedGrades": "Detailed grades", "progress.detailedGrades": "Detailed grades",
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.", "progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
"progress.footnotes.title": "Grade summary footnotes", "progress.footnotes.title": "Grade summary footnotes",
"progress.gradeSummary.grade": "Grade",
"progress.courseGrade.grades": "Grades", "progress.courseGrade.grades": "Grades",
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip", "progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
"progress.gradeSummary": "Grade summary", "progress.gradeSummary": "Grade summary",
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip", "progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your score by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.", "progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
"progress.courseGrade.label.passingGrade": "Passing grade", "progress.courseGrade.label.passingGrade": "Passing grade",
"progress.score": "Score", "progress.score": "Score",
"progress.weight": "Weight", "progress.weight": "Weight",
@@ -229,7 +241,6 @@
"notes.button.hide": "Hide Notes", "notes.button.hide": "Hide Notes",
"courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.", "courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.",
"courseCelebration.certificateBody.available": "\n Showcase your accomplishment on LinkedIn or your resumé today.\n You can download your certificate now and access it any time from your\n {dashboardLink} and {profileLink}.", "courseCelebration.certificateBody.available": "\n Showcase your accomplishment on LinkedIn or your resumé today.\n You can download your certificate now and access it any time from your\n {dashboardLink} and {profileLink}.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "You will be able to access your certificate any time from your\n {dashboardLink} and {profileLink}.",
"courseCelebration.certificateBody.unverified": "In order to generate a certificate, you must complete ID verification.\n {idVerificationSupportLink} now.", "courseCelebration.certificateBody.unverified": "In order to generate a certificate, you must complete ID verification.\n {idVerificationSupportLink} now.",
"courseCelebration.certificateBody.upgradable": "Its not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.", "courseCelebration.certificateBody.upgradable": "Its not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.",
"courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!", "courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!",
@@ -242,7 +253,8 @@
"courseCelebration.dashboardInfo": "You can access this course and its materials on your {dashboardLink}.", "courseCelebration.dashboardInfo": "You can access this course and its materials on your {dashboardLink}.",
"courseExit.programs.applyForCredit": "Apply for credit", "courseExit.programs.applyForCredit": "Apply for credit",
"courseCelebration.certificateHeader.downloadable": "Your certificate is available!", "courseCelebration.certificateHeader.downloadable": "Your certificate is available!",
"courseCelebration.certificateHeader.notAvailable": "Your certificate will be available soon!", "courseCelebration.certificateHeader.notAvailable": "Your grade and certificate will be ready soon!",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
"courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.", "courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.",
"courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!", "courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!",
"courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate", "courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate",
@@ -300,20 +312,24 @@
"learn.contentLock.content.locked": "Content Locked", "learn.contentLock.content.locked": "Content Locked",
"learn.contentLock.complete.prerequisite": "You must complete the prerequisite: '{prereqSectionName}' to access this content.", "learn.contentLock.complete.prerequisite": "You must complete the prerequisite: '{prereqSectionName}' to access this content.",
"learn.contentLock.goToSection": "Go To Prerequisite Section", "learn.contentLock.goToSection": "Go To Prerequisite Section",
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
"learn.honorCode.name": "Honor Code",
"learn.honorCode.cancel": "Cancel",
"learn.honorCode.agree": "I agree",
"gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume", "gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}", "gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}",
"gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends", "gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends",
"gatedContent.paragraph.bulletFour": "Support our {nonProfit} mission at edX", "gatedContent.paragraph.bulletFour": "Support our {nonProfitMission} at edX",
"learn.lockPaywall.title": "Graded assignments are locked", "learn.lockPaywall.title": "Graded assignments are locked",
"learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.", "learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.",
"learn.lockPaywall.upgrade.link": "Upgrade for {currencySymbol}{price}",
"learn.lockPaywall.example.alt": "Example Certificate", "learn.lockPaywall.example.alt": "Example Certificate",
"learn.lockPaywall.list.intro": "When you upgrade, you:", "learn.lockPaywall.list.intro": "When you upgrade, you:",
"learn.lockPaywall.list.bullet1.linktext": "verified certificate", "learn.lockPaywall.list.bullet1.linktext": "verified certificate",
"learn.lockPaywall.list.bullet2.boldtext": "graded assignments", "learn.lockPaywall.list.bullet2.boldtext": "graded assignments",
"learn.lockPaywall.list.bullet3.boldtext": "Full access", "learn.lockPaywall.list.bullet3.boldtext": "Full access",
"learn.lockPaywall.list.bullet4.boldtext": "non-profit", "learn.lockPaywall.list.bullet4.boldtext": "non-profit mission",
"learn.loading.content.lock": "Loading locked content messaging...", "learn.loading.content.lock": "Loading locked content messaging...",
"learn.loading.honor.codk": "Loading honor code messaging...",
"learn.loading.learning.sequence": "Loading learning sequence...", "learn.loading.learning.sequence": "Loading learning sequence...",
"learn.course.load.failure": "There was an error loading this course.", "learn.course.load.failure": "There was an error loading this course.",
"learn.sequence.no.content": "There is no content here.", "learn.sequence.no.content": "There is no content here.",
@@ -350,17 +366,22 @@
"learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}", "learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}",
"learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}", "learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}",
"learning.upgradeButton.buttonText": "Upgrade for {pricing}", "learning.upgradeButton.buttonText": "Upgrade for {pricing}",
"learning.upgradeNowButton.buttonText": "Upgrade now for {pricing}",
"masquerade-widget.userName.error.generic": "An error has occurred; please try again.", "masquerade-widget.userName.error.generic": "An error has occurred; please try again.",
"masquerade-widget.userName.input.placeholder": "Username or email", "masquerade-widget.userName.input.placeholder": "Username or email",
"masquerade-widget.userName.input.label": "Masquerade as this user", "masquerade-widget.userName.input.label": "Masquerade as this user",
"learning.streakCelebration.header": "day streak", "learning.streakCelebration.congratulations": "Congratulations!",
"learning.streakCelebration.body": "Keep it up, youre on a roll!", "learning.streakCelebration.body": "Keep it up, youre on a roll!",
"learning.streakCelebration.button": "Keep it up", "learning.streakCelebration.button": "Keep it up",
"learning.streakCelebration.buttonSrOnly": "Close modal and continue", "learning.streakCelebration.buttonSrOnly": "Close modal and continue",
"learning.streakCelebration.buttonAA759": "Continue with course",
"learning.streakCelebration.header": "day streak",
"learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course", "learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course",
"learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average", "learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average",
"learning.streakCelebration.streakDiscountMessage": "Youve unlocked a 15% off discount when you upgrade this course for a limited time only.",
"learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.", "learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.",
"learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.", "learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.",
"learning.streakCelebration.streakAA759EndDateMessage": "Ends {date}.",
"learning.loading.failure": "There was an error loading this course.", "learning.loading.failure": "There was an error loading this course.",
"learning.loading": "Loading course page…" "learning.loading": "Loading course page…"
} }

View File

@@ -9,6 +9,10 @@
"learning.enrollment.enrollNow.Inline": "Enroll now", "learning.enrollment.enrollNow.Inline": "Enroll now",
"learning.enrollment.enrollNow.Sentence": "Enroll now.", "learning.enrollment.enrollNow.Sentence": "Enroll now.",
"learning.enrollment.success": "You've successfully enrolled in this course!", "learning.enrollment.success": "You've successfully enrolled in this course!",
"account-activation.alert.title": "Activate your account so you can log back in",
"account-activation.alert.button": "Continue to {siteName}",
"account-activation.alert.message": "We sent an email to {boldEmail} with a link to activate your account. Cant find it? Check your spam folder or\n {sendEmailTag}.",
"account-activation.resend.link": "resend the email",
"learning.logistration.alert": "To see course content, {signIn} or {register}.", "learning.logistration.alert": "To see course content, {signIn} or {register}.",
"learning.offer.upgradeNow": "Upgrade now", "learning.offer.upgradeNow": "Upgrade now",
"learning.offer.header": "Upgrade by {date} and save {percentage}% [{fullPricing}]", "learning.offer.header": "Upgrade by {date} and save {percentage}% [{fullPricing}]",
@@ -39,9 +43,9 @@
"learning.dates.badge.today": "Today", "learning.dates.badge.today": "Today",
"learning.dates.badge.unreleased": "Not yet released", "learning.dates.badge.unreleased": "Not yet released",
"learning.dates.badge.verifiedOnly": "Verified only", "learning.dates.badge.verifiedOnly": "Verified only",
"learning.outline.alert.cert.title": "We are working on generating course certificates.", "learning.outline.alert.cert.when": "This course ended on {courseEndDateFormatted}. Final grades and certificates are\n scheduled to be available after {certificateAvailableDate}.",
"learning.outline.alert.cert.when": "If you have earned a certificate, you will be able to access it {timeRemaining}. You will also be able to view your certificates on your {profileLink}.", "cert.alert.earned.unavailable.header": "Your grade and certificate will be ready soon!",
"learning.outline.alert.cert.profile": "Learner Profile", "cert.alert.earned.ready.header": "Congratulations! Your certificate is ready.",
"learning.outline.alert.end.short": "This course is ending {timeRemaining} at {courseEndTime}.", "learning.outline.alert.end.short": "This course is ending {timeRemaining} at {courseEndTime}.",
"learning.outline.alert.end.long": "Course starts {timeRemaining} on {courseStartDate}.", "learning.outline.alert.end.long": "Course starts {timeRemaining} on {courseStartDate}.",
"learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.", "learning.outline.alert.start.short": "Course starts {timeRemaining} at {courseStartTime}.",
@@ -53,7 +57,7 @@
"learning.outline.collapseAll": "Collapse all", "learning.outline.collapseAll": "Collapse all",
"learning.outline.completedAssignment": "Completed", "learning.outline.completedAssignment": "Completed",
"learning.outline.completedSection": "Completed section", "learning.outline.completedSection": "Completed section",
"learning.outline.dates": "Upcoming Dates", "learning.outline.dates": "Important dates",
"learning.outline.editGoal": "Edit goal", "learning.outline.editGoal": "Edit goal",
"learning.outline.expandAll": "Expand all", "learning.outline.expandAll": "Expand all",
"learning.outline.goal": "Goal", "learning.outline.goal": "Goal",
@@ -101,24 +105,30 @@
"learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}", "learning.proctoringPanel.onboardingButtonNotOpen": "Onboarding Opens: {releaseDate}",
"learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements", "learning.proctoringPanel.reviewRequirementsButton": "Review instructions and system requirements",
"learning.outline.sequence-due": "{description} due {assignmentDue}", "learning.outline.sequence-due": "{description} due {assignmentDue}",
"learning.outline.alert.upgradecard.verifiedCertLink": "Earn a {verifiedCertLink} of completion to showcase on your resume", "learning.outline.widgets.upgradeCard.verifiedCertLink": "verified certificate",
"learning.outline.alert.upgradecard.nonProfitMission": "Support our {nonProfitMission} at edX", "learning.outline.widgets.upgradeCard.verifiedCertMessage": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"learning.outline.alert.upgradecard.unlock-graded": "Unlock your access to all course activities, including {gradedAssignments}", "learning.outline.widgets.upgradeCard.nonProfitMission": "Support our {nonProfitMission} at edX",
"learning.outline.alert.upgradecard.fullAccess": "{fullAccess} to course content and materials, even after the course ends", "learning.outline.widgets.upgradeCard.gradedAssignments": "graded assignments",
"learning.outline.alert.upgradecard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.", "learning.upgradeCard.verifiedCertLink": "Full access",
"learning.outline.alert.upgradecard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.", "learning.upgradeCard.nonProfitMission": "non-profit mission",
"learning.outline.alert.upgradecard.expiration.days": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left", "learning.outline.widgets.upgradeCard.unlockGraded": "Unlock your access to all course activities, including {gradedAssignments}",
"learning.outline.alert.upgradecard.expiration.hours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left", "learning.outline.widgets.upgradeCard.fullAccess": "{fullAccess} to course content and materials, even after the course ends",
"learning.outline.alert.upgradecard.expiration.minutes": "Less than 1 hour left", "learning.upgradeCard.expirationAccessLoss.progress": "including any progress",
"learning.outline.alert.upgradecard.expirationr": "Course access will expire {date}", "learning.outline.widgets.upgradeCard.expirationVerifiedCert.benefits": "benefits of upgrading",
"learning.outline.alert.upgradecard.code": "Use code {code} at checkout", "learning.outline.widgets.upgradeCard.expirationAccessLoss": "You will lose all access to this course, {includingAnyProgress}, on {date}.",
"learning.outline.alert.upgradecard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount", "learning.outline.widgets.upgradeCard.expirationVerifiedCert": "Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the {benefitsOfUpgrading}.",
"learning.outline.alert.upgradecard.accessExpiration": "Upgrade your course today", "learning.outline.widgets.upgradeCard.expirationDays": "{dayCount, number} {dayCount, plural, \n one {day}\n other {days}} left",
"learning.outline.alert.upgradecard.accessExpirationUrgent": "Course Access Expiration", "learning.outline.widgets.upgradeCard.expirationHours": "{hourCount, number} {hourCount, plural,\n one {hour}\n other {hours}} left",
"learning.outline.alert.upgradecard.pursueAverifiedCertificate": "Pursue a verified certificate", "learning.outline.widgets.upgradeCard.expirationMinutes": "Less than 1 hour left",
"learning.outline.widgets.upgradeCard.expiration": "Course access will expire {date}",
"learning.outline.widgets.upgradeCard.code": "Use code {code} at checkout",
"learning.outline.widgets.upgradeCard.firstTimeLearnerDiscount": "{percentage}% First-Time Learner Discount",
"learning.outline.widgets.upgradeCard.accessExpiration": "Upgrade your course today",
"learning.outline.widgets.upgradeCard.accessExpirationUrgent": "Course Access Expiration",
"learning.outline.widgets.upgradeCard.pursueAverifiedCertificate": "Pursue a verified certificate",
"progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.", "progress.certificateStatus.unverifiedBody": "In order to generate a certificate, you must complete ID verification. {idVerificationSupportLink}.",
"progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.", "progress.certificateStatus.downloadableBody": "Showcase your accomplishment on LinkedIn or your resume today. You can download your certificate now and access it any time from your Dashboard and Profile.",
"courseCelebration.certificateBody.notAvailable.endDate": "After this course officially ends on {endDate}, you will receive an\n email notification with your certificate. Once you have your certificate, be sure\n to showcase your accomplishment on LinkedIn or your resumé.", "courseCelebration.certificateBody.notAvailable.endDate": "This course ended on {endDate} and final grades and certificates are scheduled to be\n available after {certAvailableDate}.",
"progress.certificateStatus.notPassingHeader": "Certificate status", "progress.certificateStatus.notPassingHeader": "Certificate status",
"progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.", "progress.certificateStatus.notPassingBody": "In order to qualify for a certificate, you must have a passing grade.",
"progress.certificateStatus.inProgressHeader": "More content is coming soon!", "progress.certificateStatus.inProgressHeader": "More content is coming soon!",
@@ -147,7 +157,7 @@
"progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.", "progress.completion.donut.percentIncomplete": "You have not completed {percent}% of content in this course that you have access to.",
"progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.", "progress.completion.donut.percentLocked": "{percent}% of content in this course is locked and available only for those who upgrade.",
"progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.", "progress.ungradedAlert": "For progress on ungraded aspects of the course, view your {outlineLink}.",
"progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score} other{# {assignmentType} scores}} will be dropped.", "progress.footnotes.droppableAssignments": "The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped.",
"progress.assignmentType": "Assignment type", "progress.assignmentType": "Assignment type",
"progress.footnotes.backToContent": "Back to content", "progress.footnotes.backToContent": "Back to content",
"progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.", "progress.courseGrade.body": "This represents your weighted grade against the grade needed to pass this course.",
@@ -157,7 +167,8 @@
"progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)", "progress.courseGrade.footer.passing": "Youre currently passing this course with a grade of {letterGrade} ({minGrade}-{maxGrade}%)",
"progress.courseGrade.preview.header": "locked feature", "progress.courseGrade.preview.header": "locked feature",
"progress.courseGrade.preview.header.ariaHidden": "Preview of a ", "progress.courseGrade.preview.header.ariaHidden": "Preview of a ",
"progress.courseGrade.preview.body": "Unlock to view grades and work towards a certificate", "progress.courseGrade.preview.body.unlockCertificate": "Unlock to view grades and work towards a certificate.",
"progress.courseGrade.preview.body.upgradeDeadlinePassed": "The deadline to upgrade in this course has passed.",
"progress.courseGrade.preview.button.upgrade": "Upgrade now", "progress.courseGrade.preview.button.upgrade": "Upgrade now",
"progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:", "progress.courseGrade.gradeRange.tooltip": "Grade ranges for this course:",
"progress.courseOutline": "Course Outline", "progress.courseOutline": "Course Outline",
@@ -165,11 +176,12 @@
"progress.detailedGrades": "Detailed grades", "progress.detailedGrades": "Detailed grades",
"progress.detailedGrades.emptyTable": "You currently have no graded problem scores.", "progress.detailedGrades.emptyTable": "You currently have no graded problem scores.",
"progress.footnotes.title": "Grade summary footnotes", "progress.footnotes.title": "Grade summary footnotes",
"progress.gradeSummary.grade": "Grade",
"progress.courseGrade.grades": "Grades", "progress.courseGrade.grades": "Grades",
"progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip", "progress.courseGrade.gradeRange.Tooltip": "Grade range tooltip",
"progress.gradeSummary": "Grade summary", "progress.gradeSummary": "Grade summary",
"progress.gradeSummary.tooltip.alt": "Grade summary tooltip", "progress.gradeSummary.tooltip.alt": "Grade summary tooltip",
"progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your score by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.", "progress.gradeSummary.tooltip.body": "Your course assignment's weight is determined by your instructor. By multiplying your grade by the weight for that assignment type, your weighted grade is calculated. Your weighted grade is what's used to determine if you pass the course.",
"progress.courseGrade.label.passingGrade": "Passing grade", "progress.courseGrade.label.passingGrade": "Passing grade",
"progress.score": "Score", "progress.score": "Score",
"progress.weight": "Weight", "progress.weight": "Weight",
@@ -229,7 +241,6 @@
"notes.button.hide": "Hide Notes", "notes.button.hide": "Hide Notes",
"courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.", "courseExit.catalogSearchSuggestion": "Looking to learn more? {searchOurCatalogLink} to find more courses and programs to explore.",
"courseCelebration.certificateBody.available": "\n Showcase your accomplishment on LinkedIn or your resumé today.\n You can download your certificate now and access it any time from your\n {dashboardLink} and {profileLink}.", "courseCelebration.certificateBody.available": "\n Showcase your accomplishment on LinkedIn or your resumé today.\n You can download your certificate now and access it any time from your\n {dashboardLink} and {profileLink}.",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "You will be able to access your certificate any time from your\n {dashboardLink} and {profileLink}.",
"courseCelebration.certificateBody.unverified": "In order to generate a certificate, you must complete ID verification.\n {idVerificationSupportLink} now.", "courseCelebration.certificateBody.unverified": "In order to generate a certificate, you must complete ID verification.\n {idVerificationSupportLink} now.",
"courseCelebration.certificateBody.upgradable": "Its not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.", "courseCelebration.certificateBody.upgradable": "Its not too late to upgrade. For {price} you will unlock access to all graded\n assignments in this course. Upon completion, you will receive a verified certificate which is a\n valuable credential to improve your job prospects and advance your career, or highlight your\n certificate in school applications.",
"courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!", "courseCelebration.upgradeDiscountCodePrompt": "Use code {code} at checkout for {percent}% off!",
@@ -242,7 +253,8 @@
"courseCelebration.dashboardInfo": "You can access this course and its materials on your {dashboardLink}.", "courseCelebration.dashboardInfo": "You can access this course and its materials on your {dashboardLink}.",
"courseExit.programs.applyForCredit": "Apply for credit", "courseExit.programs.applyForCredit": "Apply for credit",
"courseCelebration.certificateHeader.downloadable": "Your certificate is available!", "courseCelebration.certificateHeader.downloadable": "Your certificate is available!",
"courseCelebration.certificateHeader.notAvailable": "Your certificate will be available soon!", "courseCelebration.certificateHeader.notAvailable": "Your grade and certificate will be ready soon!",
"courseCelebration.certificateBody.notAvailable.accessCertificate": "If you have earned a passing grade, your certificate will be automatically issued.",
"courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.", "courseCelebration.certificateHeader.unverified": "You must complete verification to receive your certificate.",
"courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!", "courseCelebration.certificateHeader.requestable": "Congratulations, you qualified for a certificate!",
"courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate", "courseCelebration.certificateHeader.upgradable": "Upgrade to pursue a verified certificate",
@@ -300,20 +312,24 @@
"learn.contentLock.content.locked": "Content Locked", "learn.contentLock.content.locked": "Content Locked",
"learn.contentLock.complete.prerequisite": "You must complete the prerequisite: '{prereqSectionName}' to access this content.", "learn.contentLock.complete.prerequisite": "You must complete the prerequisite: '{prereqSectionName}' to access this content.",
"learn.contentLock.goToSection": "Go To Prerequisite Section", "learn.contentLock.goToSection": "Go To Prerequisite Section",
"learn.honorCode.content": "Honesty and academic integrity are important to {siteName} and the institutions providing courses and programs on the {siteName} site. By clicking “I agree” below, I confirm that I have read, understand, and will abide by the {link} for the {siteName} Site.",
"learn.honorCode.name": "Honor Code",
"learn.honorCode.cancel": "Cancel",
"learn.honorCode.agree": "I agree",
"gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume", "gatedContent.paragraph.bulletOne": "Earn a {verifiedCertLink} of completion to showcase on your resume",
"gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}", "gatedContent.paragraph.bulletTwo": "Unlock access to all course activities, including {gradedAssignments}",
"gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends", "gatedContent.paragraph.bulletThree": "{fullAccess} to course content and materials, even after the course ends",
"gatedContent.paragraph.bulletFour": "Support our {nonProfit} mission at edX", "gatedContent.paragraph.bulletFour": "Support our {nonProfitMission} at edX",
"learn.lockPaywall.title": "Graded assignments are locked", "learn.lockPaywall.title": "Graded assignments are locked",
"learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.", "learn.lockPaywall.content": "Upgrade to gain access to locked features like this one and get the most out of your course.",
"learn.lockPaywall.upgrade.link": "Upgrade for {currencySymbol}{price}",
"learn.lockPaywall.example.alt": "Example Certificate", "learn.lockPaywall.example.alt": "Example Certificate",
"learn.lockPaywall.list.intro": "When you upgrade, you:", "learn.lockPaywall.list.intro": "When you upgrade, you:",
"learn.lockPaywall.list.bullet1.linktext": "verified certificate", "learn.lockPaywall.list.bullet1.linktext": "verified certificate",
"learn.lockPaywall.list.bullet2.boldtext": "graded assignments", "learn.lockPaywall.list.bullet2.boldtext": "graded assignments",
"learn.lockPaywall.list.bullet3.boldtext": "Full access", "learn.lockPaywall.list.bullet3.boldtext": "Full access",
"learn.lockPaywall.list.bullet4.boldtext": "non-profit", "learn.lockPaywall.list.bullet4.boldtext": "non-profit mission",
"learn.loading.content.lock": "Loading locked content messaging...", "learn.loading.content.lock": "Loading locked content messaging...",
"learn.loading.honor.codk": "Loading honor code messaging...",
"learn.loading.learning.sequence": "Loading learning sequence...", "learn.loading.learning.sequence": "Loading learning sequence...",
"learn.course.load.failure": "There was an error loading this course.", "learn.course.load.failure": "There was an error loading this course.",
"learn.sequence.no.content": "There is no content here.", "learn.sequence.no.content": "There is no content here.",
@@ -350,17 +366,22 @@
"learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}", "learning.offer.screenReaderPrices": "Original price: {originalPrice}, discount price: {discountedPrice}",
"learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}", "learning.upgradeButton.screenReaderInlinePrices": "Original price: {originalPrice}",
"learning.upgradeButton.buttonText": "Upgrade for {pricing}", "learning.upgradeButton.buttonText": "Upgrade for {pricing}",
"learning.upgradeNowButton.buttonText": "Upgrade now for {pricing}",
"masquerade-widget.userName.error.generic": "An error has occurred; please try again.", "masquerade-widget.userName.error.generic": "An error has occurred; please try again.",
"masquerade-widget.userName.input.placeholder": "Username or email", "masquerade-widget.userName.input.placeholder": "Username or email",
"masquerade-widget.userName.input.label": "Masquerade as this user", "masquerade-widget.userName.input.label": "Masquerade as this user",
"learning.streakCelebration.header": "day streak", "learning.streakCelebration.congratulations": "Congratulations!",
"learning.streakCelebration.body": "Keep it up, youre on a roll!", "learning.streakCelebration.body": "Keep it up, youre on a roll!",
"learning.streakCelebration.button": "Keep it up", "learning.streakCelebration.button": "Keep it up",
"learning.streakCelebration.buttonSrOnly": "Close modal and continue", "learning.streakCelebration.buttonSrOnly": "Close modal and continue",
"learning.streakCelebration.buttonAA759": "Continue with course",
"learning.streakCelebration.header": "day streak",
"learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course", "learning.streakCelebration.factoidABoldedSection": "are 20x more likely to pass their course",
"learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average", "learning.streakCelebration.factoidBBoldedSection": "complete 5x as much course content on average",
"learning.streakCelebration.streakDiscountMessage": "Youve unlocked a 15% off discount when you upgrade this course for a limited time only.",
"learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.", "learning.streakcelebration.factoida": "Users who learn {streak_length} days in a row {bolded_section} than those who dont.",
"learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.", "learning.streakcelebration.factoidb": "Users who learn {streak_length} days in a row {bolded_section} vs. those who dont.",
"learning.streakCelebration.streakAA759EndDateMessage": "Ends {date}.",
"learning.loading.failure": "There was an error loading this course.", "learning.loading.failure": "There was an error loading this course.",
"learning.loading": "Loading course page…" "learning.loading": "Loading course page…"
} }

View File

@@ -44,7 +44,12 @@ subscribe(APP_READY, () => {
<DatesTab /> <DatesTab />
</TabContainer> </TabContainer>
</PageRoute> </PageRoute>
<PageRoute path="/course/:courseId/progress"> <PageRoute
path={[
'/course/:courseId/progress/:userId/',
'/course/:courseId/progress',
]}
>
<TabContainer tab="progress" fetch={fetchProgressTab} slice="courseHome"> <TabContainer tab="progress" fetch={fetchProgressTab} slice="courseHome">
<ProgressTab /> <ProgressTab />
</TabContainer> </TabContainer>

View File

@@ -158,6 +158,8 @@ export async function initializeTestStore(options = {}, overrideStore = true) {
sequenceMetadata.forEach(metadata => { sequenceMetadata.forEach(metadata => {
const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${metadata.item_id}`; const sequenceMetadataUrl = `${getConfig().LMS_BASE_URL}/api/courseware/sequence/${metadata.item_id}`;
axiosMock.onGet(sequenceMetadataUrl).reply(200, metadata); axiosMock.onGet(sequenceMetadataUrl).reply(200, metadata);
const proctoredExamApiUrl = `${getConfig().LMS_BASE_URL}/api/edx_proctoring/v1/proctored_exam/attempt/course_id/${courseMetadata.id}/content_id/${sequenceMetadata.item_id}?is_learning_mfe=true`;
axiosMock.onGet(proctoredExamApiUrl).reply(200, { exam: {}, active_attempt: {} });
}); });
logUnhandledRequests(axiosMock); logUnhandledRequests(axiosMock);

View File

@@ -90,8 +90,8 @@ function StreakModal({
}; };
offer = { offer = {
discountedPrice: (mode.price * 0.85).toFixed(2).toString(), discountedPrice: `${verifiedMode.currencySymbol}${(mode.price * 0.85).toFixed(2).toString()}`,
originalPrice: mode.price.toString(), originalPrice: `${verifiedMode.currencySymbol}${mode.price.toString()}`,
upgradeUrl: mode.upgradeUrl, upgradeUrl: mode.upgradeUrl,
}; };
} }
@@ -143,7 +143,7 @@ function StreakModal({
id="learning.streakCelebration.streakAA759EndDateMessage" id="learning.streakCelebration.streakAA759EndDateMessage"
defaultMessage="Ends {date}." defaultMessage="Ends {date}."
values={{ values={{
date: new Date('2021-6-25 00:00').toLocaleDateString({ timeZone: 'UTC' }), date: new Date('2021-7-20 00:00').toLocaleDateString({ timeZone: 'UTC' }),
}} }}
/> />
</div> </div>
@@ -188,22 +188,24 @@ function StreakModal({
StreakModal.defaultProps = { StreakModal.defaultProps = {
isStreakCelebrationOpen: false, isStreakCelebrationOpen: false,
streakLengthToCelebrate: -1,
verifiedMode: {},
AA759ExperimentEnabled: false, AA759ExperimentEnabled: false,
}; };
StreakModal.propTypes = { StreakModal.propTypes = {
courseId: PropTypes.string.isRequired, courseId: PropTypes.string.isRequired,
metadataModel: PropTypes.string.isRequired, metadataModel: PropTypes.string.isRequired,
streakLengthToCelebrate: PropTypes.number.isRequired, streakLengthToCelebrate: PropTypes.number,
intl: intlShape.isRequired, intl: intlShape.isRequired,
isStreakCelebrationOpen: PropTypes.bool, isStreakCelebrationOpen: PropTypes.bool,
closeStreakCelebration: PropTypes.func.isRequired, closeStreakCelebration: PropTypes.func.isRequired,
AA759ExperimentEnabled: PropTypes.bool, AA759ExperimentEnabled: PropTypes.bool,
verifiedMode: PropTypes.shape({ verifiedMode: PropTypes.shape({
currencySymbol: PropTypes.string.isRequired, currencySymbol: PropTypes.string,
price: PropTypes.number.isRequired, price: PropTypes.number,
upgradeUrl: PropTypes.string.isRequired, upgradeUrl: PropTypes.string,
}).isRequired, }),
}; };
export default injectIntl(StreakModal); export default injectIntl(StreakModal);

View File

@@ -55,7 +55,7 @@ describe('Loaded Tab Page', () => {
const testStore = await initializeTestStore({ courseMetadata }, false); const testStore = await initializeTestStore({ courseMetadata }, false);
render(<StreakModal {...mockData} courseId={courseMetadata.id} />, { store: testStore }); render(<StreakModal {...mockData} courseId={courseMetadata.id} />, { store: testStore });
expect(screen.getByText('Youve unlocked a 15% off discount when you upgrade this course for a limited time only.')).toBeInTheDocument(); expect(screen.getByText('Youve unlocked a 15% off discount when you upgrade this course for a limited time only.')).toBeInTheDocument();
expect(screen.getByText('Ends 6/25/2021.')).toBeInTheDocument(); expect(screen.getByText('Ends 7/20/2021.')).toBeInTheDocument();
expect(screen.getByText('Continue with course')).toBeInTheDocument(); expect(screen.getByText('Continue with course')).toBeInTheDocument();
}); });
}); });

View File

@@ -45,7 +45,7 @@ function LoadedTabPage({
return ( return (
<> <>
<Helmet> <Helmet>
<title>{`${activeTab.title} | ${title} | ${getConfig().SITE_NAME}`}</title> <title>{`${activeTab ? `${activeTab.title} | ` : ''}${title} | ${getConfig().SITE_NAME}`}</title>
</Helmet> </Helmet>
<Header <Header
courseOrg={org} courseOrg={org}

View File

@@ -13,7 +13,7 @@ export default function TabContainer(props) {
tab, tab,
} = props; } = props;
const { courseId: courseIdFromUrl } = useParams(); const { courseId: courseIdFromUrl, userId } = useParams();
const dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
// The courseId from the URL is the course we WANT to load. // The courseId from the URL is the course we WANT to load.
@@ -31,6 +31,7 @@ export default function TabContainer(props) {
<TabPage <TabPage
activeTabSlug={tab} activeTabSlug={tab}
courseId={courseId} courseId={courseId}
userId={userId}
courseStatus={courseStatus} courseStatus={courseStatus}
metadataModel={`${slice}Meta`} metadataModel={`${slice}Meta`}
> >