Compare commits

...

13 Commits

Author SHA1 Message Date
Stanislav
5b676626e3 feat: Restore jump nav and content width (#995) 2024-12-24 10:53:22 +05:00
Stanislav
1fcb7622e7 fix: Add ID attribute to the main content (#1020)
Co-authored-by: Stanislav Lunyachek <lunyachek@MacBook-Pro-M1.local>
2024-04-01 14:12:44 +05:00
Dmytro
2463a1f5fb fix: Fixed the display of the selection of available time zones. (#965)
Co-authored-by: Dima Alipov <dimaalipov@MacBook-Pro-Dima.local>
2024-01-01 23:27:53 +05:00
Ihor Romaniuk
88ef2dce10 fix: trim long text in links in the social networks block (#927) 2023-10-30 14:08:44 +05:00
Taras Lytvynenko
da7e956840 fix: error when trying to save 'other education' (#926) 2023-10-25 00:06:56 +05:00
renovate[bot]
4d15524ab6 fix(deps): update dependency @edx/frontend-platform to v5.5.4 2023-10-17 19:00:25 +05:00
Muhammad Abdullah Waheed
f098fe1a3a feat: babel-plugin-react-intl to babel-plugin-formatjs migration (#894)
* feat: babel-plugin-react-intl to babel-plugin-formatjs migration

* fix: upgradfed frontend-build to fix security issue
2023-10-10 17:05:08 +05:00
edx-transifex-bot
a989fabb92 chore(i18n): update translations (#902)
Co-authored-by: Jenkins <sre+jenkins@edx.org>
2023-10-10 16:12:18 +05:00
sundasnoreen12
1257e81781 Merge pull request #901 from openedx/sundas/INF-1086
fix: fixed preferences toggle unpredictable behavior
2023-10-10 11:03:10 +05:00
renovate[bot]
b08890b794 fix(deps): update dependency @edx/frontend-component-header to v4.7.0 2023-10-09 09:33:28 +00:00
sundasnoreen12
7f1c7b86ef fix: fixed preferences toggle unpredictable behaviour 2023-10-09 11:52:58 +05:00
renovate[bot]
a5fd3a7f7e fix(deps): update dependency @edx/frontend-component-footer to v12.3.0 2023-10-09 06:15:44 +00:00
Deborah Kaplan
4513cc8834 feat: remove (long-disabled) coaching functionality (#891)
* Removes the coaching functionality
* no tests are referencing this
* Leaves behind a decision record referencing the creation of the
  coaching functionality (0002-coaching-addition)

FIXES: APER-2408-Remove-Coaching-functionality-from-the-Account-MFE
2023-10-04 18:36:09 +05:00
47 changed files with 1094 additions and 1565 deletions

1
.env
View File

@@ -1,6 +1,5 @@
ACCESS_TOKEN_COOKIE_NAME=''
BASE_URL=''
COACHING_ENABLED=''
CREDENTIALS_BASE_URL=''
CSRF_TOKEN_API_PATH=''
DEMOGRAPHICS_BASE_URL=''

View File

@@ -1,6 +1,5 @@
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='localhost:1997'
COACHING_ENABLED=''
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DEMOGRAPHICS_BASE_URL='http://localhost:18360'

View File

@@ -1,6 +1,5 @@
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
BASE_URL='localhost:1997'
COACHING_ENABLED=''
CREDENTIALS_BASE_URL='http://localhost:18150'
CSRF_TOKEN_API_PATH='/csrf/api/v1/token'
DEMOGRAPHICS_BASE_URL='http://localhost:18360'

View File

@@ -8,7 +8,7 @@ i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
transifex_temp = ./temp/babel-plugin-formatjs
NPM_TESTS=build i18n_extract lint test

View File

@@ -75,12 +75,6 @@ edX-specific Environment Variables
Furthermore, there are several edX-specific environment variables that enable integrations with closed-source services private to the edX organization, and are unsupported in Open edX. Enabling these environment variables will result in undefined behavior in Open edX installations:
``COACHING_ENABLED``
Example: ``true`` | ``''`` (empty strings are falsy)
Enables support for a section of the micro-frontend that helps users arrange for coaching sessions. Integrates with a private coaching plugin and is only used by edx.org.
``ENABLE_DEMOGRAPHICS_COLLECTION``
Example: ``true`` | ``''`` (empty strings are falsy)

513
package-lock.json generated
View File

@@ -10,9 +10,9 @@
"license": "AGPL-3.0",
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.1",
"@edx/frontend-platform": "5.4.0",
"@edx/frontend-component-footer": "12.3.0",
"@edx/frontend-component-header": "4.7.0",
"@edx/frontend-platform": "5.5.4",
"@edx/paragon": "20.46.2",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
@@ -63,7 +63,7 @@
},
"devDependencies": {
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-build": "12.9.17",
"@edx/frontend-build": "13.0.1",
"@edx/reactifex": "1.1.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",
@@ -2050,9 +2050,9 @@
}
},
"node_modules/@edx/frontend-build": {
"version": "12.9.17",
"resolved": "https://registry.npmjs.org/@edx/frontend-build/-/frontend-build-12.9.17.tgz",
"integrity": "sha512-P4w/jp456o5EQ5l0WVl4JIRHnC5xbG+M1Ce7xX1JKex4YDN3lmoBuiaguPg3n6CBDsNKEiBr48n4u+ug+5UTCw==",
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-build/-/frontend-build-13.0.1.tgz",
"integrity": "sha512-XoR2Yt9FUXKfbah0AirS5ckARc+j0jLzTRtvpsaqg4bFhPIbv0HragqoLs8Y8SQfn4sZy2v6EZ3pA09P4OBwDg==",
"dependencies": {
"@babel/cli": "7.22.5",
"@babel/core": "7.22.5",
@@ -2064,13 +2064,14 @@
"@babel/preset-react": "7.22.5",
"@edx/eslint-config": "3.2.0",
"@edx/new-relic-source-map-webpack-plugin": "2.1.0",
"@formatjs/cli": "^6.0.3",
"@fullhuman/postcss-purgecss": "5.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
"@svgr/webpack": "8.1.0",
"autoprefixer": "10.4.16",
"babel-jest": "26.6.3",
"babel-loader": "9.1.3",
"babel-plugin-react-intl": "7.9.4",
"babel-plugin-formatjs": "^10.4.0",
"babel-plugin-transform-imports": "2.0.0",
"babel-polyfill": "6.26.0",
"chalk": "4.1.2",
@@ -2092,7 +2093,7 @@
"image-minimizer-webpack-plugin": "3.8.3",
"jest": "26.6.3",
"mini-css-extract-plugin": "1.6.2",
"postcss": "8.4.30",
"postcss": "8.4.31",
"postcss-custom-media": "10.0.1",
"postcss-loader": "7.3.3",
"postcss-rtlcss": "4.0.8",
@@ -2183,15 +2184,17 @@
}
},
"node_modules/@edx/frontend-component-footer": {
"version": "12.2.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.2.1.tgz",
"integrity": "sha512-0ZeuFsnToS7h7qI4yXo6FKVz+c4vDyqP2nhPMR3xm3xgPOmHvf8KNL6ES/YGb+ptPYb64ZxT2iNBP6DY0wF3uQ==",
"version": "12.3.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-12.3.0.tgz",
"integrity": "sha512-ivCtioyP4SceYM4/ugVtif4c41Y+epA0NM7sSB/x6s9A/RTQXb2TY3fDc9lB3ah/0+pRwGVJJEVYkPAZ4JdC/g==",
"dependencies": {
"@edx/paragon": "^21.3.1",
"@fortawesome/fontawesome-svg-core": "6.4.2",
"@fortawesome/free-brands-svg-icons": "6.4.2",
"@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/react-fontawesome": "0.2.0"
"@fortawesome/react-fontawesome": "0.2.0",
"lodash": "^4.17.21"
},
"peerDependencies": {
"@edx/frontend-platform": "^4.0.0 || ^5.0.0",
@@ -2200,6 +2203,61 @@
"react-dom": "^16.9.0 || ^17.0.0"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/@edx/paragon": {
"version": "21.3.1",
"resolved": "https://registry.npmjs.org/@edx/paragon/-/paragon-21.3.1.tgz",
"integrity": "sha512-bXTUaOEmT8XLnDQzYS8QLMvWK5K2BN4jHlx25lO8N0XWRQeDiQTdbx8OrEbv8QOPTlrv0an5MZc+qjlleJFObg==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.1.18",
"@popperjs/core": "^2.11.4",
"bootstrap": "^4.6.2",
"chalk": "^4.1.2",
"child_process": "^1.0.2",
"classnames": "^2.3.1",
"email-prop-type": "^3.0.0",
"file-selector": "^0.6.0",
"font-awesome": "^4.7.0",
"glob": "^8.0.3",
"inquirer": "^8.2.5",
"lodash.uniqby": "^4.7.0",
"mailto-link": "^2.0.0",
"prop-types": "^15.8.1",
"react-bootstrap": "^1.6.5",
"react-colorful": "^5.6.1",
"react-dropzone": "^14.2.1",
"react-focus-on": "^3.5.4",
"react-loading-skeleton": "^3.1.0",
"react-popper": "^2.2.5",
"react-proptype-conditional-require": "^1.0.4",
"react-responsive": "^8.2.0",
"react-table": "^7.7.0",
"react-transition-group": "^4.4.2",
"tabbable": "^5.3.3",
"uncontrollable": "^7.2.1",
"uuid": "^9.0.0"
},
"bin": {
"paragon": "bin/paragon-scripts.js"
},
"peerDependencies": {
"react": "^16.8.6 || ^17.0.0",
"react-dom": "^16.8.6 || ^17.0.0",
"react-intl": "^5.25.1 || ^6.4.0"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/@edx/paragon/node_modules/@fortawesome/react-fontawesome": {
"version": "0.1.19",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.19.tgz",
"integrity": "sha512-Hyb+lB8T18cvLNX0S3llz7PcSOAJMLwiVKBuuzwM/nI5uoBw+gQjnf9il0fR1C3DKOI5Kc79pkJ4/xB0Uw9aFQ==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.x"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
@@ -2257,10 +2315,111 @@
"node": ">=6"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/@edx/frontend-component-footer/node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@edx/frontend-component-footer/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@edx/frontend-component-header": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.6.1.tgz",
"integrity": "sha512-DE19gbiV+fjeLiTAVaf+qDqsRrDpGXuwho1xne7qutdrrcDaX9DKJBV/iNRhr/PS4kpzQu4jPtr6Fdr3gWzu6g==",
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-4.7.0.tgz",
"integrity": "sha512-hJCWfdEed8h7aRKmo5lCMyemtMR272q/g1WOetMy8C8ZzeNvf8t94WPFCK4OlbHWTQBd5UiaqGWQWmaGXm0jVg==",
"dependencies": {
"@edx/paragon": "21.1.10",
"@fortawesome/fontawesome-svg-core": "6.4.2",
@@ -2494,9 +2653,9 @@
}
},
"node_modules/@edx/frontend-platform": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-5.4.0.tgz",
"integrity": "sha512-cz9yQfHJk1PMQdhxeyIXXiBNqaG9dQZpcBgodmVlLnL/PeN1CuRVjjW98WlKYSrxoZAH5wdgUOr0hKRW3OyBAA==",
"version": "5.5.4",
"resolved": "https://registry.npmjs.org/@edx/frontend-platform/-/frontend-platform-5.5.4.tgz",
"integrity": "sha512-Yum+oST7XfDwDnDhBnzeR/mjp6O+G0g+5AZtIJ1BdTKQH1z9FObfim/pfoiI9STiYlguVpeWMkzWuca/QMLO/Q==",
"dependencies": {
"@cospired/i18n-iso-languages": "4.1.0",
"@formatjs/intl-pluralrules": "4.3.3",
@@ -2788,6 +2947,25 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@formatjs/cli": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.2.0.tgz",
"integrity": "sha512-sP04UpocRHYwSovUnunAZHYvCTVbNcaLtWKnr1lETGRUnRRQqnXy/3d2Ce271ELXmNUSde2eHRdu4rv2XaVaiQ==",
"bin": {
"formatjs": "bin/formatjs"
},
"engines": {
"node": ">= 16"
},
"peerDependencies": {
"vue": "^3.3.4"
},
"peerDependenciesMeta": {
"vue": {
"optional": true
}
}
},
"node_modules/@formatjs/ecma402-abstract": {
"version": "1.11.4",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
@@ -2874,23 +3052,6 @@
"tslib": "^2.1.0"
}
},
"node_modules/@formatjs/intl-numberformat": {
"version": "5.7.6",
"resolved": "https://registry.npmjs.org/@formatjs/intl-numberformat/-/intl-numberformat-5.7.6.tgz",
"integrity": "sha512-ZlZfYtvbVHYZY5OG3RXizoCwxKxEKOrzEe2YOw9wbzoxF3PmFn0SAgojCFGLyNXkkR6xVxlylhbuOPf1dkIVNg==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.4.0",
"tslib": "^2.0.1"
}
},
"node_modules/@formatjs/intl-numberformat/node_modules/@formatjs/ecma402-abstract": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.4.0.tgz",
"integrity": "sha512-Mv027hcLFjE45K8UJ8PjRpdDGfR0aManEFj1KzoN8zXNveHGEygpZGfFf/FTTMl+QEVSrPAUlyxaCApvmv47AQ==",
"dependencies": {
"tslib": "^2.0.1"
}
},
"node_modules/@formatjs/intl-pluralrules": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.3.3.tgz",
@@ -2911,42 +3072,6 @@
"tslib": "^2.1.0"
}
},
"node_modules/@formatjs/ts-transformer": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-2.13.0.tgz",
"integrity": "sha512-mu7sHXZk1NWZrQ3eUqugpSYo8x5/tXkrI4uIbFqCEC0eNgQaIcoKgVeDFgDAcgG+cEme2atAUYSFF+DFWC4org==",
"dependencies": {
"intl-messageformat-parser": "6.1.2",
"tslib": "^2.0.1",
"typescript": "^4.0"
},
"peerDependencies": {
"ts-jest": "^26.4.0"
},
"peerDependenciesMeta": {
"ts-jest": {
"optional": true
}
}
},
"node_modules/@formatjs/ts-transformer/node_modules/@formatjs/ecma402-abstract": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.5.0.tgz",
"integrity": "sha512-wXv36yo+mfWllweN0Fq7sUs7PUiNopn7I0JpLTe3hGu6ZMR4CV7LqK1llhB18pndwpKoafQKb1et2DCJAOW20Q==",
"dependencies": {
"tslib": "^2.0.1"
}
},
"node_modules/@formatjs/ts-transformer/node_modules/intl-messageformat-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-6.1.2.tgz",
"integrity": "sha512-4GQDEPhl/ZMNDKwMsLqyw1LG2IAWjmLJXdmnRcHKeLQzpgtNYZI6lVw1279pqIkRk2MfKb9aDsVFzm565azK5A==",
"deprecated": "We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser",
"dependencies": {
"@formatjs/ecma402-abstract": "1.5.0",
"tslib": "^2.0.1"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz",
@@ -5770,6 +5895,14 @@
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@types/babel__helper-plugin-utils/-/babel__helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-6RaT7i6r2rT6ouIDZ2Cd6dPkq4wn1F8pLyDO+7wPVsL1dodvORiZORImaD6j9FBcHjPGuERE0hhtwkuPNXsO0A==",
"dependencies": {
"@types/babel__core": "*"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz",
@@ -5871,14 +6004,6 @@
"@types/send": "*"
}
},
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/glob": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
@@ -5991,6 +6116,11 @@
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz",
"integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA=="
},
"node_modules/@types/json-stable-stringify": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz",
"integrity": "sha512-s2cfwagOQAS8o06TcwKfr9Wx11dNGbH2E9vJz1cqV+a/LOyhWNLUNd6JSRYNzvB4d29UuJX2M0Dj9vE1T8fRXw=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -6099,15 +6229,6 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
"node_modules/@types/schema-utils": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/schema-utils/-/schema-utils-2.4.0.tgz",
"integrity": "sha512-454hrj5gz/FXcUE20ygfEiN4DxZ1sprUo0V1gqIqkNZ/CzoEzAZEll2uxMsuyz6BYjiQan4Aa65xbTemfzW9hQ==",
"deprecated": "This is a stub types definition. schema-utils provides its own type definitions, so you do not need this installed.",
"dependencies": {
"schema-utils": "*"
}
},
"node_modules/@types/seedrandom": {
"version": "2.4.30",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.30.tgz",
@@ -7174,6 +7295,151 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/babel-plugin-formatjs": {
"version": "10.5.6",
"resolved": "https://registry.npmjs.org/babel-plugin-formatjs/-/babel-plugin-formatjs-10.5.6.tgz",
"integrity": "sha512-XlE8WHF/ZstS5K3ZCWb5nQ6e9u6KpNquTpHpjteGaMSguSjvbfNb7CsF4YHq1fTPBdHWNspA3qfAqMGgHBO4mw==",
"dependencies": {
"@babel/core": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-jsx": "7",
"@babel/traverse": "7",
"@babel/types": "^7.12.11",
"@formatjs/icu-messageformat-parser": "2.6.2",
"@formatjs/ts-transformer": "3.13.5",
"@types/babel__core": "^7.1.7",
"@types/babel__helper-plugin-utils": "^7.10.0",
"@types/babel__traverse": "^7.1.7",
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/@formatjs/ecma402-abstract": {
"version": "1.17.2",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.2.tgz",
"integrity": "sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg==",
"dependencies": {
"@formatjs/intl-localematcher": "0.4.2",
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/@formatjs/icu-messageformat-parser": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.6.2.tgz",
"integrity": "sha512-nF/Iww7sc5h+1MBCDRm68qpHTCG4xvGzYs/x9HFcDETSGScaJ1Fcadk5U/NXjXeCtzD+DhN4BAwKFVclHfKMdA==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.17.2",
"@formatjs/icu-skeleton-parser": "1.6.2",
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/@formatjs/icu-skeleton-parser": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.6.2.tgz",
"integrity": "sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA==",
"dependencies": {
"@formatjs/ecma402-abstract": "1.17.2",
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/@formatjs/intl-localematcher": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.4.2.tgz",
"integrity": "sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA==",
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/@formatjs/ts-transformer": {
"version": "3.13.5",
"resolved": "https://registry.npmjs.org/@formatjs/ts-transformer/-/ts-transformer-3.13.5.tgz",
"integrity": "sha512-dh2mmZqkId0UeM+FQtmwugpMGvyzTBmXj5LjwD4M5OeSm62tcgkScjqeO/1EetaNS/JkTUBbsFBnHzaDzh3yOw==",
"dependencies": {
"@formatjs/icu-messageformat-parser": "2.6.2",
"@types/json-stable-stringify": "^1.0.32",
"@types/node": "14 || 16 || 17",
"chalk": "^4.0.0",
"json-stable-stringify": "^1.0.1",
"tslib": "^2.4.0",
"typescript": "^4.7 || 5"
},
"peerDependencies": {
"ts-jest": ">=27"
},
"peerDependenciesMeta": {
"ts-jest": {
"optional": true
}
}
},
"node_modules/babel-plugin-formatjs/node_modules/@types/node": {
"version": "17.0.45",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
},
"node_modules/babel-plugin-formatjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/babel-plugin-formatjs/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/babel-plugin-formatjs/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/babel-plugin-formatjs/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/babel-plugin-formatjs/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/babel-plugin-formatjs/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/babel-plugin-istanbul": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
@@ -7239,41 +7505,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/babel-plugin-react-intl": {
"version": "7.9.4",
"resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-7.9.4.tgz",
"integrity": "sha512-cMKrHEXrw43yT4M89Wbgq8A8N8lffSquj1Piwov/HVukR7jwOw8gf9btXNsQhT27ccyqEwy+M286JQYy0jby2g==",
"deprecated": "this package has been renamed to babel-plugin-formatjs",
"dependencies": {
"@babel/core": "^7.9.0",
"@babel/helper-plugin-utils": "^7.8.3",
"@babel/types": "^7.9.5",
"@formatjs/ts-transformer": "^2.6.0",
"@types/babel__core": "^7.1.7",
"@types/fs-extra": "^9.0.1",
"@types/schema-utils": "^2.4.0",
"fs-extra": "^9.0.0",
"intl-messageformat-parser": "^5.3.7",
"schema-utils": "^2.6.6"
}
},
"node_modules/babel-plugin-react-intl/node_modules/schema-utils": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
"dependencies": {
"@types/json-schema": "^7.0.5",
"ajv": "^6.12.4",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 8.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/babel-plugin-transform-imports": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-imports/-/babel-plugin-transform-imports-2.0.0.tgz",
@@ -12225,15 +12456,6 @@
"tslib": "^2.1.0"
}
},
"node_modules/intl-messageformat-parser": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-5.5.1.tgz",
"integrity": "sha512-TvB3LqF2VtP6yI6HXlRT5TxX98HKha6hCcrg9dwlPwNaedVNuQA9KgBdtWKgiyakyCTYHQ+KJeFEstNKfZr64w==",
"deprecated": "We've written a new parser that's 6x faster and is backwards compatible. Please use @formatjs/icu-messageformat-parser",
"dependencies": {
"@formatjs/intl-numberformat": "^5.5.2"
}
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -16518,6 +16740,17 @@
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"node_modules/json-stable-stringify": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz",
"integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==",
"dependencies": {
"jsonify": "^0.0.1"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -16545,6 +16778,14 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jsonify": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
"integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/jsx-ast-utils": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
@@ -18363,9 +18604,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.30",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz",
"integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==",
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",

View File

@@ -10,7 +10,7 @@
},
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
@@ -28,9 +28,9 @@
],
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.2.0",
"@edx/frontend-component-footer": "12.2.1",
"@edx/frontend-component-header": "4.6.1",
"@edx/frontend-platform": "5.4.0",
"@edx/frontend-component-footer": "12.3.0",
"@edx/frontend-component-header": "4.7.0",
"@edx/frontend-platform": "5.5.4",
"@edx/paragon": "20.46.2",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
@@ -81,7 +81,7 @@
},
"devDependencies": {
"@edx/browserslist-config": "1.2.0",
"@edx/frontend-build": "12.9.17",
"@edx/frontend-build": "13.0.1",
"@edx/reactifex": "1.1.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",

View File

@@ -48,7 +48,6 @@ import {
getStatesList,
} from './data/constants';
import { fetchSiteLanguages } from './site-language';
import CoachingToggle from './coaching/CoachingToggle';
import DemographicsSection from './demographics/DemographicsSection';
import { fetchCourseList } from '../notification-preferences/data/thunks';
import { withLocation, withNavigate } from './hoc';
@@ -689,15 +688,6 @@ class AccountSettingsPage extends React.Component {
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])}
{...editableFieldProps}
/>
{getConfig().COACHING_ENABLED
&& this.props.formValues.coaching.eligible_for_coaching
&& (
<CoachingToggle
name="coaching"
phone_number={this.props.formValues.phone_number}
coaching={this.props.formValues.coaching}
/>
)}
</div>
{getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && this.renderDemographicsSection()}
<div className="account-section pt-3 mb-5" id="social-media">
@@ -824,12 +814,12 @@ class AccountSettingsPage extends React.Component {
</h1>
<div>
<div className="row">
<div className="col-md-2">
<div className="col-md-3">
<JumpNav
displayDemographicsLink={this.props.formValues.shouldDisplayDemographicsSection}
/>
</div>
<div className="col-md-10">
<div className="col-md-9">
{loading ? this.renderLoading() : null}
{loaded ? this.renderContent() : null}
{loadingError ? this.renderError() : null}
@@ -867,11 +857,6 @@ AccountSettingsPage.propTypes = {
social_link_facebook: PropTypes.string,
social_link_twitter: PropTypes.string,
time_zone: PropTypes.string,
coaching: PropTypes.shape({
coaching_consent: PropTypes.bool.isRequired,
user: PropTypes.number.isRequired,
eligible_for_coaching: PropTypes.bool.isRequired,
}),
state: PropTypes.string,
shouldDisplayDemographicsSection: PropTypes.bool,
useVerifiedNameForCerts: PropTypes.bool.isRequired,

View File

@@ -411,8 +411,8 @@ const messages = defineMessages({
defaultMessage: 'No formal education',
description: 'Selected by the user to describe their education.',
},
'account.settings.field.education.levels.o': {
id: 'account.settings.field.education.levels.o',
'account.settings.field.education.levels.other': {
id: 'account.settings.field.education.levels.other',
defaultMessage: 'Other education',
description: 'Selected by the user if they have a type of education not described by the other choices.',
},

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Button, Form, StatefulButton,
@@ -155,7 +156,7 @@ const EditableField = (props) => {
</Button>
) : null}
</div>
<p data-hj-suppress className={isGrayedOut ? 'grayed-out' : null}>{renderValue(value)}</p>
<p data-hj-suppress className={classNames('text-truncate', { 'grayed-out': isGrayedOut })}>{renderValue(value)}</p>
<p className="small text-muted mt-n2">{renderConfirmationMessage() || helpText}</p>
</div>
),

View File

@@ -98,9 +98,28 @@ const EditableSelectField = (props) => {
value: confirmationValue,
});
};
const selectOptions = options.map(option => (
<option value={option.value} key={`${option.value}-${option.label}`}>{option.label}</option>
));
const selectOptions = options.map((option) => {
if (option.group) {
// If the option has a 'group' property, it represents an element with sub-options.
return (
<optgroup label={option.label} key={option.label}>
{option.group.map((subOption) => (
<option
value={subOption.value}
key={`${subOption.value}-${subOption.label}`}
>
{subOption.label}
</option>
))}
</optgroup>
);
}
return (
<option value={option.value} key={`${option.value}-${option.label}`}>
{option.label}
</option>
);
});
return (
<SwitchContent

View File

@@ -20,7 +20,7 @@ const JumpNav = ({
const showPreferences = useSelector(selectShowPreferences());
return (
<div className={classNames('jump-nav px-2.25', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<div className={classNames('jump-nav', { 'jump-nav-sm position-sticky pt-3': stickToTop })}>
<Scrollspy
items={[
'basic-information',

View File

@@ -1,267 +0,0 @@
import React from 'react';
import { getConfig, getQueryParameters } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import get from 'lodash.get';
import { getAuthenticatedHttpClient, getAuthenticatedUser } from '@edx/frontend-platform/auth';
import PageLoading from '../PageLoading';
import CoachingConsentForm from './CoachingConsentForm';
import messages from './CoachingConsent.messages';
import LogoSVG from '../../logo.svg';
import { fetchSettings } from '../data/actions';
import { coachingConsentPageSelector } from '../data/selectors';
const Logo = ({ src, alt, ...attributes }) => <img src={src} alt={alt} {...attributes} />;
const SuccessMessage = (props) => (
<div className="col-12 col-lg-6 shadow-lg mx-auto mt-4 p-5">
<FontAwesomeIcon className="text-success" icon={faCheck} size="5x" />
<div className="h3">{props.header}</div>
<div>{props.message}</div>
<Hyperlink destination={props.continueUrl} className="d-block p-2 my-3 text-center text-white bg-primary rounded">
{props.continue}
</Hyperlink>
</div>
);
const AutoRedirect = (props) => {
window.location.href = props.redirectUrl;
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
};
const VIEWS = {
NOT_LOADED: 'NOT_LOADED',
LOADED: 'LOADED',
SUCCESS: 'SUCCESS',
SUCCESS_PENDING: 'SUCCESS_PENDING',
DECLINED: 'DECLINED',
DECLINE_PENDING: 'DECLINE_PENDING',
};
class CoachingConsent extends React.Component {
constructor(props, context) {
super(props, context);
// Used to redirect back to the courseware.
const nextUrl = this.sanitizeForwardingUrl(getQueryParameters().next);
this.state = {
redirectUrl: nextUrl || `${getConfig().LMS_BASE_URL}/dashboard/`,
formErrors: {},
formSubmitted: false,
declineSubmitted: false,
submissionSuccess: false,
};
this.handleSubmit = this.handleSubmit.bind(this);
this.declineCoaching = this.declineCoaching.bind(this);
this.patchUsingCoachingConsentForm = this.patchUsingCoachingConsentForm.bind(this);
}
componentDidMount() {
this.props.fetchSettings();
}
handleSubmit(e) {
e.preventDefault();
const fullName = e.target.fullName.value;
const phoneNumber = e.target.phoneNumber.value;
const body = {
coaching_consent: true,
consent_form_seen: true,
phone_number: phoneNumber,
full_name: fullName,
};
this.setState({
formErrors: {},
formSubmitted: true,
declineSubmitted: false,
}, () => this.patchUsingCoachingConsentForm(body));
}
sanitizeForwardingUrl(url) {
// Redirect to root of MFE if invalid next param is sent
return url && url.startsWith(getConfig().LMS_BASE_URL) ? url : `${getConfig().LMS_BASE_URL}/dashboard/`;
}
async patchUsingCoachingConsentForm(body) {
const { userId } = getAuthenticatedUser();
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/coaching_consent/${userId}/`;
let formErrors = {};
const data = await getAuthenticatedHttpClient()
.patch(requestUrl, body)
.catch((error) => {
if (get(error, 'customAttributes.httpErrorResponseData')) {
formErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
} else {
formErrors = { full_name: 'Something went wrong. Please try again.' };
}
this.setState({
submissionSuccess: false,
formErrors,
formSubmitted: false,
});
});
if (get(data, 'status') === 200) {
this.setState({ submissionSuccess: true });
}
}
declineCoaching(e) {
e.preventDefault();
const body = {
coaching_consent: false,
consent_form_seen: true,
};
this.setState({
formErrors: {},
formSubmitted: false,
declineSubmitted: true,
}, () => this.patchUsingCoachingConsentForm(body));
}
renderView(currentView) {
switch (currentView) {
case VIEWS.NOT_LOADED:
return <PageLoading srMessage="" />;
case VIEWS.LOADED:
return (
<CoachingConsentForm
onSubmit={this.handleSubmit}
declineCoaching={this.declineCoaching}
formErrors={this.state.formErrors}
formValues={this.props.formValues}
redirectUrl={this.state.redirectUrl}
profileDataManager={this.props.profileDataManager}
/>
);
case VIEWS.SUCCESS_PENDING:
return <PageLoading srMessage="Submitting..." />;
case VIEWS.SUCCESS:
return (
<SuccessMessage
continueUrl={this.state.redirectUrl}
header={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.header'])}
message={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.message'])}
continue={this.props.intl.formatMessage(messages['account.settings.coaching.consent.success.continue'])}
/>
);
case VIEWS.DECLINE_PENDING:
return <PageLoading srMessage="Redirecting..." />;
case VIEWS.DECLINED:
return <AutoRedirect redirectUrl={this.state.redirectUrl} />;
default:
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}
}
render() {
const { loaded } = this.props;
const formHasErrors = Object.keys(this.state.formErrors).length > 0;
let currentView = null;
// This amount of logic was making the template very hard to read, so I broke it out into views.
if (!loaded) {
currentView = VIEWS.NOT_LOADED;
} else if (this.state.formSubmitted && !formHasErrors) {
if (this.state.submissionSuccess) {
currentView = VIEWS.SUCCESS;
} else {
currentView = VIEWS.SUCCESS_PENDING;
}
} else if (this.state.declineSubmitted && !formHasErrors) {
if (this.state.submissionSuccess) {
currentView = VIEWS.DECLINED;
} else {
currentView = VIEWS.DECLINE_PENDING;
}
} else {
currentView = VIEWS.LOADED;
}
return (
<main>
<div className="w-100 d-flex justify-content-center align-items-center shadow coaching-header">
<Logo
className="logo"
src={LogoSVG}
alt="Logo"
/>
</div>
{this.renderView(currentView)}
</main>
);
}
}
Logo.defaultProps = {
src: '',
alt: '',
};
Logo.propTypes = {
src: PropTypes.string,
alt: PropTypes.string,
};
SuccessMessage.defaultProps = {
header: '',
message: '',
continueUrl: '',
continue: '',
};
SuccessMessage.propTypes = {
header: PropTypes.string,
message: PropTypes.string,
continueUrl: PropTypes.string,
continue: PropTypes.string,
};
AutoRedirect.defaultProps = {
redirectUrl: '',
};
AutoRedirect.propTypes = {
redirectUrl: PropTypes.string,
};
CoachingConsent.defaultProps = {
loaded: false,
profileDataManager: null,
};
CoachingConsent.propTypes = {
intl: intlShape.isRequired,
loaded: PropTypes.bool,
formValues: PropTypes.shape({
name: PropTypes.string,
phone_number: PropTypes.string,
coaching: PropTypes.shape({
coaching_consent: PropTypes.bool.isRequired,
user: PropTypes.number.isRequired,
eligible_for_coaching: PropTypes.bool.isRequired,
consent_form_seen: PropTypes.bool.isRequired,
}),
}).isRequired,
formErrors: PropTypes.shape({
coaching: PropTypes.shape({}),
}).isRequired,
confirmationValues: PropTypes.shape({
coaching: PropTypes.shape({}),
name: PropTypes.shape({}),
phone_number: PropTypes.shape({}),
}).isRequired,
fetchSettings: PropTypes.func.isRequired,
profileDataManager: PropTypes.string,
};
export default connect(coachingConsentPageSelector, {
fetchSettings,
})(injectIntl(CoachingConsent));

View File

@@ -1,66 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'account.settings.coaching.consent.welcome.header': {
id: 'account.settings.coaching.consent.welcome.header',
defaultMessage: 'Lets get started.',
description: 'The welcome header for consent form.',
},
'account.settings.coaching.consent.welcome.subheader': {
id: 'account.settings.coaching.consent.welcome.subheader',
defaultMessage: "We're here for you from start to finish",
description: 'The welcome subheader for consent form.',
},
'account.settings.coaching.consent.description': {
id: 'account.settings.coaching.consent.description',
defaultMessage: "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
description: 'Text describing what Coaching is.',
},
'account.settings.coaching.consent.text-messaging.disclaimer': {
id: 'account.settings.coaching.consent.text-messaging.disclaimer',
defaultMessage: '* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.',
description: 'Text describing what Coaching is.',
},
'account.settings.coaching.consent.accept-coaching': {
id: 'account.settings.coaching.consent.accept-coaching',
defaultMessage: 'Sign up for coaching',
description: 'Text to confirm coaching enablement',
},
'account.settings.coaching.consent.decline-coaching': {
id: 'account.settings.coaching.consent.decline-coaching',
defaultMessage: 'I prefer not to be contacted with free coaching services',
description: 'Text to decline coaching enablement',
},
'account.settings.coaching.consent.label.name': {
id: 'account.settings.coaching.consent.label.name',
defaultMessage: 'Please confirm your name',
description: 'Label for name input',
},
'account.settings.coaching.consent.label.phone-number': {
id: 'account.settings.coaching.consent.label.phone-number',
defaultMessage: 'Enter your mobile number',
description: 'Label for mobile phone number input',
},
'account.settings.coaching.consent.success.header': {
id: 'account.settings.coaching.consent.success.header',
defaultMessage: 'Success!',
description: 'Heading announcing that submission succeeded',
},
'account.settings.coaching.consent.success.message': {
id: 'account.settings.coaching.consent.success.message',
defaultMessage: "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
description: 'Text announcing that you have signed up and will receive texts',
},
'account.settings.coaching.consent.success.continue': {
id: 'account.settings.coaching.consent.success.continue',
defaultMessage: 'Start my course',
description: 'Text that the user will be sent back to the courseware',
},
'account.settings.coaching.managed.support': {
id: 'account.settings.coaching.managed.support',
defaultMessage: 'support',
description: 'website support',
},
});
export default messages;

View File

@@ -1,129 +0,0 @@
import React from 'react';
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
import { Form, Button, Hyperlink } from '@edx/paragon';
import PropTypes from 'prop-types';
import Alert from '../Alert';
import messages from './CoachingConsent.messages';
const ErrorMessage = (props) => <div className="alert-warning mb-2">{props.message}</div>;
const ManagedProfileAlert = ({ profileDataManager }) => (
<Alert className="alert alert-primary" role="alert">
<FormattedMessage
id="account.settings.coaching.managed.alert"
defaultMessage="Your name is managed by {managerTitle}. Contact your administrator for help."
description="Alert message informing the user their account data is managed by a third party"
values={{
managerTitle: <b>{profileDataManager}</b>,
}}
/>
</Alert>
);
const CoachingForm = (props) => (
<div className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg">
<h2 className="h2">
{props.intl.formatMessage(messages['account.settings.coaching.consent.welcome.header'])}
</h2>
<p>{props.intl.formatMessage(messages['account.settings.coaching.consent.description'])}</p>
<div>
<form onSubmit={props.onSubmit}>
<div className="py-3">
{!!props.profileDataManager && (
<ManagedProfileAlert profileDataManager={props.profileDataManager} />
)}
<ErrorMessage message={props.formErrors.full_name} />
<label className="h6" htmlFor="fullName">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.name'])}
</label>
<Form.Control
type="text"
name="full-name"
id="fullName"
disabled={!!props.profileDataManager}
defaultValue={props.formValues.name}
/>
</div>
<div className="py-3">
<ErrorMessage message={props.formErrors.phone_number} />
<label className="h6" htmlFor="phoneNumber">
{props.intl.formatMessage(messages['account.settings.coaching.consent.label.phone-number'])}
</label>
<Form.Control
type="text"
name="phone_number"
id="phoneNumber"
defaultValue={props.formValues.phone_number}
/>
</div>
<div className=" py-3">
<p className="small font-italic">
{props.intl.formatMessage(messages['account.settings.coaching.consent.text-messaging.disclaimer'])}
</p>
</div>
<ErrorMessage message={props.formErrors.coaching} />
<div className="d-flex flex-column align-items-center">
<Button variant="outline-primary" className="w-100" type="submit">
{props.intl.formatMessage(messages['account.settings.coaching.consent.accept-coaching'])}
</Button>
</div>
<div className="mt-3">
<Hyperlink
className="mt-3 text-dark btn-link small"
destination={props.redirectUrl}
onClick={props.declineCoaching}
>
{props.intl.formatMessage(messages['account.settings.coaching.consent.decline-coaching'])}
</Hyperlink>
</div>
</form>
</div>
</div>
);
CoachingForm.defaultProps = {
formErrors: {
coaching: '',
name: '',
phone_number: '',
},
};
CoachingForm.propTypes = {
intl: intlShape.isRequired,
onSubmit: PropTypes.func.isRequired,
declineCoaching: PropTypes.func.isRequired,
formValues: PropTypes.shape({
name: PropTypes.string,
phone_number: PropTypes.string,
coaching: PropTypes.shape({
coaching_consent: PropTypes.bool.isRequired,
user: PropTypes.number.isRequired,
eligible_for_coaching: PropTypes.bool.isRequired,
consent_form_seen: PropTypes.bool.isRequired,
}),
}).isRequired,
formErrors: PropTypes.shape({
coaching: PropTypes.string,
full_name: PropTypes.string,
phone_number: PropTypes.string,
}),
redirectUrl: PropTypes.string.isRequired,
profileDataManager: PropTypes.string.isRequired,
};
ErrorMessage.defaultProps = {
message: '',
};
ErrorMessage.propTypes = {
message: PropTypes.string,
};
ManagedProfileAlert.propTypes = {
profileDataManager: PropTypes.string.isRequired,
};
export default injectIntl(CoachingForm);

View File

@@ -1,103 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import messages from './CoachingToggle.messages';
import { editableFieldSelector } from '../data/selectors';
import { saveSettings, updateDraft, saveMultipleSettings } from '../data/actions';
import EditableField from '../EditableField';
const CoachingToggle = (props) => (
<>
<EditableField
name="phone_number"
type="text"
value={props.phone_number}
label={props.intl.formatMessage(messages['account.settings.field.phone_number'])}
emptyLabel={props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
onChange={props.updateDraft}
onSubmit={() => {
const { coaching } = props;
if (coaching.coaching_consent === true) {
return props.saveMultipleSettings([
{
formId: 'coaching',
commitValues: {
...coaching,
phone_number: props.phone_number,
},
},
{
formId: 'phone_number',
commitValues: props.phone_number,
},
], 'phone_number');
}
return props.saveSettings('phone_number', props.phone_number);
}}
/>
<Form.Group
isInvalid={!!props.error}
className="custom-control custom-switch"
>
<Form.Switch
name={props.name}
className="custom-control-input"
disabled={props.saveState === 'pending'}
type="checkbox"
id="coachingConsent"
checked={props.coaching.coaching_consent}
helperText={props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
value={props.coaching.coaching_consent}
onChange={async (e) => {
const { name } = e.target;
// eslint-disable-next-line camelcase
const { user, eligible_for_coaching } = props.coaching;
const value = {
user,
// eslint-disable-next-line camelcase
eligible_for_coaching,
coaching_consent: e.target.checked,
};
props.saveSettings(name, value);
}}
>
{props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}
</Form.Switch>
{!!props.error && (
<Form.Control.Feedback>
{props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
</Form.Control.Feedback>
)}
</Form.Group>
</>
);
CoachingToggle.defaultProps = {
phone_number: '',
error: '',
saveState: undefined,
};
CoachingToggle.propTypes = {
name: PropTypes.string.isRequired,
error: PropTypes.string,
coaching: PropTypes.shape({
coaching_consent: PropTypes.bool.isRequired,
user: PropTypes.number.isRequired,
eligible_for_coaching: PropTypes.bool.isRequired,
}).isRequired,
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
saveSettings: PropTypes.func.isRequired,
saveMultipleSettings: PropTypes.func.isRequired,
updateDraft: PropTypes.func.isRequired,
intl: intlShape.isRequired,
phone_number: PropTypes.string,
};
export default connect(editableFieldSelector, {
saveSettings,
updateDraft,
saveMultipleSettings,
})(injectIntl(CoachingToggle));

View File

@@ -1,31 +0,0 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'account.settings.field.phone_number': {
id: 'account.settings.field.phone_number',
defaultMessage: 'Phone Number',
description: 'The label for a phone numbers setting in the user profile',
},
'account.settings.field.phone_number.empty': {
id: 'account.settings.field.phone_number.empty',
defaultMessage: 'Add a phone number',
description: 'placeholder for a profiles empty phone number field',
},
'account.settings.field.coaching_consent': {
id: 'account.settings.field.coaching_consent',
defaultMessage: 'Coaching consent',
description: 'The label for the coaching consent setting in the user profile',
},
'account.settings.field.coaching_consent.tooltip': {
id: 'account.settings.field.coaching_consent.tooltip',
defaultMessage: 'MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.',
description: 'A tooltip explaining what coaching is and who it is for',
},
'account.settings.field.coaching_consent.error': {
id: 'account.settings.field.coaching_consent.error',
defaultMessage: 'A valid US phone number is required to opt into coaching',
description: 'An error message that displays when a user attempts to consent to coaching without first providing a phone number in their profile',
},
});
export default messages;

View File

@@ -1,51 +0,0 @@
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getConfig } from '@edx/frontend-platform';
import get from 'lodash.get';
/**
* get all settings related to the coaching plugin. Settings used
* by Microbachelors students.
* @param {Number} userId users are identified in the api by LMS id
*/
export async function getCoachingPreferences(userId) {
let data = {};
try {
({ data } = await getAuthenticatedHttpClient()
.get(`${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`));
} catch (error) {
// If a user isn't active the API call will fail with a lack of credentials.
data = {
coaching_consent: false,
user: userId,
eligible_for_coaching: false,
consent_form_seen: false,
};
}
return data;
}
/**
* patch all of the settings related to coaching.
* @param {Number} userId users are identified in the api by LMS id
* @param {Object} commitValues { coaching }
*/
export async function patchCoachingPreferences(userId, commitValues) {
const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`;
const { coaching } = commitValues;
coaching.user = userId;
await getAuthenticatedHttpClient()
.patch(requestUrl, coaching)
.catch((error) => {
const apiError = Object.create(error);
apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData);
if (get(apiError, 'fieldErrors.phone_number')) {
// eslint-disable-next-line prefer-destructuring
apiError.fieldErrors.coaching = apiError.fieldErrors.phone_number[0];
delete apiError.fieldErrors.phone_number;
}
throw apiError;
});
return commitValues;
}

View File

@@ -1,103 +0,0 @@
/* eslint-disable no-import-assign */
import React from 'react';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import { act } from 'react-dom/test-utils';
import configureStore from 'redux-mock-store';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import * as auth from '@edx/frontend-platform/auth';
import CoachingConsent from '../CoachingConsent';
import * as selectors from '../../data/selectors';
jest.mock('@edx/frontend-platform/auth');
const IntlCoachingConsent = injectIntl(CoachingConsent);
jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ coachingConsentPageSelector: () => ({}) })));
const mockStore = configureStore();
describe('CoachingConsent', () => {
let props = {};
let store = {};
selectors.mockClear();
const reduxWrapper = children => (
<IntlProvider locale="en">
<Provider store={store}>{children}</Provider>
</IntlProvider>
);
beforeEach(() => {
store = mockStore();
props = {
fetchSettings: jest.fn(),
loaded: true,
saveState: undefined,
formValues: {
name: 'edx edx',
phone_number: '1234567890',
coaching: {
coaching_consent: true,
consent_form_seen: false,
eligible_for_coaching: true,
user: 1,
},
},
formErrors: {},
confirmationValues: {},
profileDataManager: '',
intl: {},
};
auth.getAuthenticatedHttpClient = jest.fn(() => ({
patch: async () => ({
data: { status: 200 },
catch: () => {},
}),
}));
auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 }));
});
it('should render', () => {
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
expect(wrapper).toMatchSnapshot();
});
it('disables name field on enterprise user', () => {
props = {
...props,
profileDataManager: 'test person',
};
const wrapper = renderer.create(reduxWrapper(<IntlCoachingConsent {...props} />)).toJSON();
expect(wrapper).toMatchSnapshot();
});
it('display completed box when successfully submitted', async () => {
const fakeEvent = {
preventDefault: () => {},
target: {
fullName: { value: 'edx edx' },
phoneNumber: { value: '9783028731' },
},
};
const wrapper = renderer.create(
reduxWrapper(<IntlCoachingConsent {...props} />),
{
// bypass the forward-ref. we don't care about focus for this one test
createNodeMock: (element) => {
if (element.type === 'button') {
// mock a focus function
return {
focus: async () => wrapper.root.findByType('form').props.onSubmit(fakeEvent),
};
}
return null;
},
},
);
const form = wrapper.root.findByType('form');
await act(async () => { await form.props.onSubmit(fakeEvent); });
expect(wrapper.toJSON()).toMatchSnapshot();
});
});

View File

@@ -1,300 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CoachingConsent disables name field on enterprise user 1`] = `
<main>
<div
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
>
<img
alt="Logo"
className="logo"
src="icon/mock/path"
/>
</div>
<div
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
>
<h2
className="h2"
>
Lets get started.
</h2>
<p>
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
</p>
<div>
<form
onSubmit={[Function]}
>
<div
className="py-3"
>
<div
className="alert d-flex align-items-start alert alert-primary"
>
<div />
<div>
Your name is managed by
<b>
test person
</b>
. Contact your administrator for help.
</div>
</div>
<div
className="alert-warning mb-2"
>
</div>
<label
className="h6"
htmlFor="fullName"
>
Please confirm your name
</label>
<div
className="pgn__form-control-decorator-group"
>
<input
className="has-value form-control"
defaultValue="edx edx"
disabled={true}
id="fullName"
name="full-name"
onBlur={[Function]}
onChange={[Function]}
type="text"
/>
</div>
</div>
<div
className="py-3"
>
<div
className="alert-warning mb-2"
>
</div>
<label
className="h6"
htmlFor="phoneNumber"
>
Enter your mobile number
</label>
<div
className="pgn__form-control-decorator-group"
>
<input
className="has-value form-control"
defaultValue="1234567890"
id="phoneNumber"
name="phone_number"
onBlur={[Function]}
onChange={[Function]}
type="text"
/>
</div>
</div>
<div
className=" py-3"
>
<p
className="small font-italic"
>
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
</p>
</div>
<div
className="alert-warning mb-2"
>
</div>
<div
className="d-flex flex-column align-items-center"
>
<button
className="w-100 btn btn-outline-primary"
disabled={false}
type="submit"
>
Sign up for coaching
</button>
</div>
<div
className="mt-3"
>
<a
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
href="http://localhost:18000/dashboard/"
onClick={[Function]}
target="_self"
>
I prefer not to be contacted with free coaching services
</a>
</div>
</form>
</div>
</div>
</main>
`;
exports[`CoachingConsent display completed box when successfully submitted 1`] = `
<main>
<div
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
>
<img
alt="Logo"
className="logo"
src="icon/mock/path"
/>
</div>
<div>
<div
className="d-flex justify-content-center align-items-center flex-column"
style={
Object {
"height": "50vh",
}
}
>
<div
className="spinner-border text-primary"
role="status"
>
<span
className="sr-only"
>
Submitting...
</span>
</div>
</div>
</div>
</main>
`;
exports[`CoachingConsent should render 1`] = `
<main>
<div
className="w-100 d-flex justify-content-center align-items-center shadow coaching-header"
>
<img
alt="Logo"
className="logo"
src="icon/mock/path"
/>
</div>
<div
className="col-12 col-md-6 col-xl-5 mx-auto mt-4 p-5 shadow-lg"
>
<h2
className="h2"
>
Lets get started.
</h2>
<p>
MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*
</p>
<div>
<form
onSubmit={[Function]}
>
<div
className="py-3"
>
<div
className="alert-warning mb-2"
>
</div>
<label
className="h6"
htmlFor="fullName"
>
Please confirm your name
</label>
<div
className="pgn__form-control-decorator-group"
>
<input
className="has-value form-control"
defaultValue="edx edx"
disabled={false}
id="fullName"
name="full-name"
onBlur={[Function]}
onChange={[Function]}
type="text"
/>
</div>
</div>
<div
className="py-3"
>
<div
className="alert-warning mb-2"
>
</div>
<label
className="h6"
htmlFor="phoneNumber"
>
Enter your mobile number
</label>
<div
className="pgn__form-control-decorator-group"
>
<input
className="has-value form-control"
defaultValue="1234567890"
id="phoneNumber"
name="phone_number"
onBlur={[Function]}
onChange={[Function]}
type="text"
/>
</div>
</div>
<div
className=" py-3"
>
<p
className="small font-italic"
>
* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.
</p>
</div>
<div
className="alert-warning mb-2"
>
</div>
<div
className="d-flex flex-column align-items-center"
>
<button
className="w-100 btn btn-outline-primary"
disabled={false}
type="submit"
>
Sign up for coaching
</button>
</div>
<div
className="mt-3"
>
<a
className="pgn__hyperlink default-link standalone-link mt-3 text-dark btn-link small"
href="http://localhost:18000/dashboard/"
onClick={[Function]}
target="_self"
>
I prefer not to be contacted with free coaching services
</a>
</div>
</form>
</div>
</div>
</main>
`;

View File

@@ -25,7 +25,7 @@ export const EDUCATION_LEVELS = [
'jhs',
'el',
'none',
'o',
'other',
];
export const GENDER_OPTIONS = [

View File

@@ -106,11 +106,6 @@ const isEditingSelector = createSelector(
(name, accountSettings) => accountSettings.openFormId === name,
);
const confirmationValuesSelector = createSelector(
accountSettingsSelector,
accountSettings => accountSettings.confirmationValues,
);
const errorSelector = createSelector(
accountSettingsSelector,
accountSettings => accountSettings.errors,
@@ -289,35 +284,6 @@ export const certPreferenceSelector = createSelector(
}),
);
export const coachingConsentPageSelector = createSelector(
accountSettingsSelector,
formValuesSelector,
activeAccountSelector,
profileDataManagerSelector,
saveStateSelector,
confirmationValuesSelector,
errorSelector,
(
accountSettings,
formValues,
activeAccount,
profileDataManager,
saveState,
confirmationValues,
errors,
) => ({
loading: accountSettings.loading,
loaded: accountSettings.loaded,
loadingError: accountSettings.loadingError,
isActive: activeAccount,
profileDataManager,
formValues,
saveState,
confirmationValues,
formErrors: errors,
}),
);
export const demographicsSectionSelector = createSelector(
formValuesSelector,
draftsSelector,

View File

@@ -0,0 +1,16 @@
import { profileDataManagerSelector } from './selectors';
const testValue = 'test VALUE';
describe('profileDataManagerSelector', () => {
it('returns the profileDataManager from the state', () => {
const state = {
accountSettings: {
profileDataManager: { testValue },
},
};
const result = profileDataManagerSelector(state);
expect(result).toEqual(state.accountSettings.profileDataManager);
});
});

View File

@@ -8,7 +8,6 @@ import isEmpty from 'lodash.isempty';
import { handleRequestError, unpackFieldErrors } from './utils';
import { getThirdPartyAuthProviders } from '../third-party-auth';
import { postVerifiedNameConfig } from '../certificate-preference/data/service';
import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service';
import { getDemographics, getDemographicsOptions, patchDemographics } from '../demographics/data/service';
import { DEMOGRAPHICS_FIELDS } from '../demographics/data/utils';
@@ -214,7 +213,7 @@ export async function postVerifiedName(data) {
/**
* A single function to GET everything considered a setting.
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
* Currently encapsulates Account, Preferences, ThirdPartyAuth, and Demographics
*/
export async function getSettings(username, userRoles, userId) {
const [
@@ -223,7 +222,6 @@ export async function getSettings(username, userRoles, userId) {
thirdPartyAuthProviders,
profileDataManager,
timeZones,
coaching,
shouldDisplayDemographicsQuestionsResponse,
demographics,
demographicsOptions,
@@ -233,7 +231,6 @@ export async function getSettings(username, userRoles, userId) {
getThirdPartyAuthProviders(),
getProfileDataManager(username, userRoles),
getTimeZones(),
getConfig().COACHING_ENABLED && getCoachingPreferences(userId),
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && shouldDisplayDemographicsQuestions(),
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographicsOptions(),
@@ -245,7 +242,6 @@ export async function getSettings(username, userRoles, userId) {
thirdPartyAuthProviders,
profileDataManager,
timeZones,
coaching,
shouldDisplayDemographicsSection: shouldDisplayDemographicsQuestionsResponse,
...demographics,
demographicsOptions,
@@ -254,26 +250,23 @@ export async function getSettings(username, userRoles, userId) {
/**
* A single function to PATCH everything considered a setting.
* Currently encapsulates Account, Preferences, coaching and ThirdPartyAuth
* Currently encapsulates Account, Preferences, ThirdPartyAuth
*/
export async function patchSettings(username, commitValues, userId) {
// Note: time_zone exists in the return value from user/v1/accounts
// but it is always null and won't update. It also exists in
// user/v1/preferences where it does update. This is the one we use.
const preferenceKeys = ['time_zone'];
const coachingKeys = ['coaching'];
const demographicsKeys = DEMOGRAPHICS_FIELDS;
const certificateKeys = ['useVerifiedNameForCerts'];
const isDemographicsKey = (value, key) => key.includes('demographics');
const accountCommitValues = omit(
commitValues,
preferenceKeys,
coachingKeys,
demographicsKeys,
certificateKeys,
);
const preferenceCommitValues = pick(commitValues, preferenceKeys);
const coachingCommitValues = pick(commitValues, coachingKeys);
const demographicsCommitValues = pickBy(commitValues, isDemographicsKey);
const certCommitValues = pick(commitValues, certificateKeys);
const patchRequests = [];
@@ -284,9 +277,6 @@ export async function patchSettings(username, commitValues, userId) {
if (!isEmpty(preferenceCommitValues)) {
patchRequests.push(patchPreferences(username, preferenceCommitValues));
}
if (!isEmpty(coachingCommitValues)) {
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
}
if (!isEmpty(demographicsCommitValues)) {
patchRequests.push(patchDemographics(userId, demographicsCommitValues));
}

View File

@@ -0,0 +1,169 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n';
import EditableSelectField from '../EditableSelectField';
const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch,
}));
jest.mock('@edx/frontend-platform/auth');
jest.mock('../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) })));
const IntlEditableSelectField = injectIntl(EditableSelectField);
const mockStore = configureStore();
describe('EditableSelectField', () => {
let props = {};
let store = {};
const reduxWrapper = children => (
<Router>
<IntlProvider locale="en">
<Provider store={store}>{children}</Provider>
</IntlProvider>
</Router>
);
beforeEach(() => {
store = mockStore();
props = {
name: 'testField',
label: 'Main Label',
emptyLabel: 'Empty Main Label',
type: 'text',
value: 'Test Field',
userSuppliedValue: '',
options: [
{
label: 'Default Option',
value: 'defaultOption',
},
{
label: 'User Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
],
},
{
label: 'Other Options',
group: [
{
label: 'Suboption 2',
value: 'suboption2',
},
{
label: 'Suboption 3',
value: 'suboption3',
},
],
},
],
saveState: 'default',
error: '',
confirmationMessageDefinition: {
id: 'confirmationMessageId',
defaultMessage: 'Default Confirmation Message',
description: 'Description of the confirmation message',
},
confirmationValue: 'Confirmation Value',
helpText: 'Helpful Text',
isEditing: false,
isEditable: true,
isGrayedOut: false,
};
});
afterEach(() => jest.clearAllMocks());
it('renders EditableSelectField correctly with editing disabled', () => {
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders EditableSelectField correctly with editing enabled', () => {
props = {
...props,
isEditing: true,
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders EditableSelectField with an error', () => {
const errorProps = {
...props,
error: 'This is an error message',
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...errorProps} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions when option has a group', () => {
const propsWithGroup = {
...props,
options: [
{
label: 'User Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
],
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithGroup} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions when option does not have a group', () => {
const propsWithoutGroup = {
...props,
options: [
{
label: 'Default Option',
value: 'defaultOption',
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithoutGroup} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('renders selectOptions with multiple groups', () => {
const propsWithGroups = {
...props,
options: [
{
label: 'Mixed Options',
group: [
{
label: 'Suboption 1',
value: 'suboption1',
},
{
label: 'Suboption 2',
value: 'suboption2',
},
],
},
],
};
const tree = renderer.create(reduxWrapper(<IntlEditableSelectField {...propsWithGroups} />)).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,485 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EditableSelectField renders EditableSelectField correctly with editing disabled 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders EditableSelectField correctly with editing enabled 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<form
onSubmit={[Function]}
>
<div
className="pgn__form-group"
>
<label
className="pgn__form-label h6 d-block"
htmlFor="field-testField"
size="sm"
>
Main Label
</label>
<div
className="pgn__form-control-decorator-group"
>
<text
aria-describedby="field-testField-2"
className="has-value form-control is-invalid"
data-hj-suppress={true}
id="field-testField"
name="testField"
onBlur={[Function]}
onChange={[Function]}
type="text"
value="Test Field"
>
<option
value="defaultOption"
>
Default Option
</option>
<optgroup
label="User Options"
>
<option
value="suboption1"
>
Suboption 1
</option>
</optgroup>
<optgroup
label="Other Options"
>
<option
value="suboption2"
>
Suboption 2
</option>
<option
value="suboption3"
>
Suboption 3
</option>
</optgroup>
</text>
</div>
<div
className="pgn__form-text pgn__form-text-default"
>
<div>
Helpful Text
</div>
</div>
<div
className="pgn__form-control-description pgn__form-text pgn__form-text-invalid"
id="field-testField-2"
>
<span
className="pgn__icon"
>
<svg
aria-hidden={true}
fill="none"
focusable={false}
height={24}
role="img"
viewBox="0 0 24 24"
width={24}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"
fill="currentColor"
/>
</svg>
</span>
<div>
</div>
</div>
</div>
<p>
<button
aria-disabled={false}
aria-live="assertive"
className="pgn__stateful-btn pgn__stateful-btn-state-default mr-2 btn btn-primary"
disabled={false}
onClick={[Function]}
type="submit"
>
<span
className="d-flex align-items-center justify-content-center"
>
<span>
Save
</span>
</span>
</button>
<button
className="btn btn-outline-primary"
disabled={false}
onClick={[Function]}
type="button"
>
Cancel
</button>
</p>
</form>
</div>
</div>
`;
exports[`EditableSelectField renders EditableSelectField with an error 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions when option does not have a group 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions when option has a group 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;
exports[`EditableSelectField renders selectOptions with multiple groups 1`] = `
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
>
<div
className="form-group"
>
<div
className="d-flex align-items-start"
>
<h6
aria-level="3"
>
Main Label
</h6>
<button
className="ml-3 btn btn-link"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-pencil-alt fa-w-16 mr-1"
data-icon="pencil-alt"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"
fill="currentColor"
style={Object {}}
/>
</svg>
Edit
</button>
</div>
<p
className={null}
data-hj-suppress={true}
>
Test Field
</p>
<p
className="small text-muted mt-n2"
>
Default Confirmation Message
</p>
</div>
</div>
</div>
`;

View File

@@ -2,7 +2,7 @@
exports[`JumpNav should not render Optional Information or delete account link 1`] = `
<div
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"
@@ -79,7 +79,7 @@ exports[`JumpNav should not render Optional Information or delete account link 1
exports[`JumpNav should render Optional Information and delete account link 1`] = `
<div
className="jump-nav px-2.25 jump-nav-sm position-sticky pt-3"
className="jump-nav jump-nav-sm position-sticky pt-3"
>
<ul
className="list-unstyled"

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "الاسم الكامل",
"account.settings.field.name.modal.certificate.option.verified": "اسم متحقَّق منه",
"account.settings.field.name.modal.certificate.button.choose": "اختيار الاسم",
"account.settings.coaching.consent.welcome.header": "لنبدأ.",
"account.settings.coaching.consent.welcome.subheader": "نحن هنا لأجلك من البداية حتى النهاية",
"account.settings.coaching.consent.description": "تتضمن برامج MicroBachelors مرافقة تركز على مهنتك و تعليمك و كيفية تحقيقك للنتائج، و ذلك من خلال التواصل الفردي مع خبير متمرس. إن كنت مهتمًا، فيرجى تزويدنا بالمعلومات أدناه و النقر على \"إرسال\"، و سيتصل بك شريكنا في المرافقة عبر البريد الإلكتروني و/أو الرسائل النصية لمساعدتك على المضي قدمًا. تنطبق الشروط والأحكام.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* خدمات المرافقة مشمولة دون تكاليف إضافية للمتعلمين الذين لديهم أرقام هواتف أمريكية. تتضمن المرافقة رسائل نصية دورية. قد تنطبق أسعار على الرسائل والبيانات. أرسل STOP للانسحاب.",
"account.settings.coaching.consent.accept-coaching": "سجّل للاستفادة من خدمات المرافقة",
"account.settings.coaching.consent.decline-coaching": "أفضّل ألا يٌتصَل بي بخصوص خدمات المرافقة المجانية",
"account.settings.coaching.consent.label.name": "رجاءً أكّد اسمك",
"account.settings.coaching.consent.label.phone-number": "أدخل رقم هاتفك الجوّال",
"account.settings.coaching.consent.success.header": "نجحت العملية!",
"account.settings.coaching.consent.success.message": "أنت الآن مشترك في المرافقة. ترقّب رسالة عبر البريد الإلكتروني أو خدمة الرسائل القصيرة في في الأيام المقبلة.",
"account.settings.coaching.consent.success.continue": "البدء في مساقي",
"account.settings.coaching.managed.support": "الدعم",
"account.settings.coaching.managed.alert": "اسمك يديره {ManagerTitle}. اتصل بمديرك للحصول على المساعدة.",
"account.settings.field.phone_number": "رقم الهاتف",
"account.settings.field.phone_number.empty": "إضافة رقم هاتف",
"account.settings.field.coaching_consent": "الموافقة على المرافقة",
"account.settings.field.coaching_consent.tooltip": "تتضمن برامج MicroBachelors مرافقة قائمة على الرسائل النصية، تساعدك على إقران تجاربك التعلّمية مع أهدافك المهنية من خلال النصائح الفردية. خدمات المرافقة مشمولة دون تكاليف إضافية، وهي متوفرة للمتعلمين الذين لديهم أرقام هواتف جوالة أمريكية. تنطبق أسعار المراسلة القياسية. أرسل 'STOP' في أي وقت للانسحاب من الرسائل.",
"account.settings.field.coaching_consent.error": "مطلوب رقم هاتف أمريكي صحيح للتسجيل في المرافقة",
"account.settings.delete.account.before.proceeding": "قبل المتابعة، يرجى {actionLink}.",
"account.settings.delete.account.header": "حذف حسابي",
"account.settings.delete.account.subheader": "نأسف لذهابك!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Vollständiger Name",
"account.settings.field.name.modal.certificate.option.verified": "Verifizierter Name",
"account.settings.field.name.modal.certificate.button.choose": "Wähle Name",
"account.settings.coaching.consent.welcome.header": "Starten Sie jetzt.",
"account.settings.coaching.consent.welcome.subheader": "Wir begleiten Sie von Anfang bis Ende. ",
"account.settings.coaching.consent.description": "Zu den MicroBachelors-Programmen gehört ein Coaching, das sich auf Ihre Karriere, Ihre Ausbildung und die Art und Weise konzentriert, wie Sie durch persönliche Kommunikation mit einem erfahrenen Fachmann Ergebnisse erzielen. Wenn Sie interessiert sind, geben Sie die folgenden Informationen ein und klicken Sie auf \"Senden\". Unser Coaching-Partner wird sich per E-Mail und / oder SMS mit Ihnen in Verbindung setzen, um Sie voranzubringen. Geschäftsbedingungen gelten.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Die Coaching Dienste erfordern keine zusätzlichen Kosten für Teilnehmer mit US-Telefonnummern. Das Coaching beinhaltet regelmäßige SMS-Nachrichten. Es gelten die üblichen Tarife für SMS-Nachrichten. Mit dem Text STOP können Sie sich jederzeit abmelden.",
"account.settings.coaching.consent.accept-coaching": "Anmeldung zum Coaching ",
"account.settings.coaching.consent.decline-coaching": "Ich bevorzuge es, ohne den coaching Service fortzufahren",
"account.settings.coaching.consent.label.name": "Bitte bestätigen Sie Ihren Namen",
"account.settings.coaching.consent.label.phone-number": "Bitte geben Sie Ihre Handynummer an",
"account.settings.coaching.consent.success.header": "Super!",
"account.settings.coaching.consent.success.message": "Sie sind nun für das Coaching angemeldet. Sie erhalten eine SMS-Nachricht als Bestätigung in den kommenden Tagen.",
"account.settings.coaching.consent.success.continue": "Kurs starten",
"account.settings.coaching.managed.support": "Support",
"account.settings.coaching.managed.alert": "Ihr Name wird von {managerTitle} verwaltet. Wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten.",
"account.settings.field.phone_number": "Telefonnummer",
"account.settings.field.phone_number.empty": "Eine Telefonnummer hinzufügen",
"account.settings.field.coaching_consent": "Einverständnis zum Coaching",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors-Programme umfassen SMS-basiertes Coaching, das Ihnen hilft, Bildungserfahrungen mit Ihren Karrierezielen durch persönliche Beratung zu verbinden. Coaching-Services sind ohne zusätzliche Kosten enthalten und stehen Lernenden mit US-Handynummern zur Verfügung. Es gelten die Standard-Messaging-Gebühren. Senden Sie jederzeit eine SMS mit „STOP“, um Nachrichten abzubestellen.",
"account.settings.field.coaching_consent.error": "Eine gültige Telefonnummer ist nötig, um sich für das Coaching anzumelden",
"account.settings.delete.account.before.proceeding": "Bevor Sie fortfahren, bitte {actionLink}.",
"account.settings.delete.account.header": "Meinen Account löschen",
"account.settings.delete.account.subheader": "Es tut uns leid, dass Sie Ihren Account löschen möchten!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Nombre completo",
"account.settings.field.name.modal.certificate.option.verified": "Nombre verificado",
"account.settings.field.name.modal.certificate.button.choose": "Escoge un nombre",
"account.settings.coaching.consent.welcome.header": "Empecemos",
"account.settings.coaching.consent.welcome.subheader": "Estamos aquí para ustede desde el inicio hasta el final",
"account.settings.coaching.consent.description": "Los programas de MicroBachelors incluyen entrenamiento que se enfoca en su carrera, educación y cómo logrará resultados a través de la comunicación individual con un profesional experimentado. Si está interesado, proporcione la información a continuación y haga clic en \"Enviar\", y nuestro socio asesor se comunicará con usted por correo electrónico y / o mensaje de texto para ayudarlo a avanzar. Los términos y Condiciones aplican.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Los servicios de entrenamiento se incluyen sin costo adicional para los alumnos con números de teléfono de EE. UU. El entrenamiento incluye mensajes de texto recurrentes. Se pueden aplicar tarifas por mensajes y datos. Envía STOP para cancelar la suscripción.",
"account.settings.coaching.consent.accept-coaching": "Registrarse para coaching",
"account.settings.coaching.consent.decline-coaching": "Prefiero no ser contactado con servicios de coaching gratuitos.",
"account.settings.coaching.consent.label.name": "Por favor confirme su nombre",
"account.settings.coaching.consent.label.phone-number": "Ingrese su número de teléfono móvil",
"account.settings.coaching.consent.success.header": "¡Éxito!",
"account.settings.coaching.consent.success.message": "Estás inscrito para coaching. Puedes esperar un mensaje por correo electrónico o SMS en los próximos días.",
"account.settings.coaching.consent.success.continue": "Iniciar mi curso",
"account.settings.coaching.managed.support": "soporte",
"account.settings.coaching.managed.alert": "{ManagerTitle} administra su Nombre. Póngase en contacto con su administrador para obtener ayuda.",
"account.settings.field.phone_number": "Teléfono",
"account.settings.field.phone_number.empty": "Añadir un número de teléfono",
"account.settings.field.coaching_consent": "Consentimiento de coaching",
"account.settings.field.coaching_consent.tooltip": "Los programas de MicroBachelors incluyen entrenamiento basado en mensajes de texto que lo ayuda a emparejar experiencias educativas con sus objetivos profesionales a través de asesoramiento personalizado. Los servicios de entrenamiento se incluyen sin costo adicional y están disponibles para estudiantes con números de teléfono móvil de EE. UU. Se aplican tarifas de mensajería estándar. Envíe \"STOP\" en cualquier momento para cancelar la suscripción a los mensajes.",
"account.settings.field.coaching_consent.error": "Se requiere un número de teléfono válido de EE. UU. Para optar por el coaching",
"account.settings.delete.account.before.proceeding": "Antes de continuar, por favor {actionLink}.",
"account.settings.delete.account.header": "Eliminar mi cuenta",
"account.settings.delete.account.subheader": "¡Sentimos que te vayas!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "نام و نام خانوادگی",
"account.settings.field.name.modal.certificate.option.verified": "تغییر نام",
"account.settings.field.name.modal.certificate.button.choose": "انتخاب نام",
"account.settings.coaching.consent.welcome.header": "بیا آغاز کنیم.",
"account.settings.coaching.consent.welcome.subheader": "ما از آغاز تا پایان با شما هستیم",
"account.settings.coaching.consent.description": "برنامه‌های MicroBachelors شامل مربیگری است که بر حرفه، تحصیلات و نحوه دستیابی به نتایج از طریق ارتباط یک‌به‌یک با یک متخصص با تجربه تمرکز دارد. اگر علاقه‌مند هستید، اطلاعات زیر را ارائه دهید و روی «ارسال» کلیک کنید، و شریک مربی ما از طریق رایانامه و/یا پیام متنی با شما در ارتباط خواهد بود تا به شما در حرکت به جلو کمک کند. شرایط و ضوابط اعمال می‌شود.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* خدمات مربیگری بدون هزینه اضافی برای یادگیرندگان با شماره تلفن ایالات متحده گنجانده شده است. مربی‌گری شامل پیام‌های متنی مکرر است. ممکن است نرخ پیام و داده اعمال شود. برای انصراف، STOP را پیامک کنید.",
"account.settings.coaching.consent.accept-coaching": "ثبت‌نام برای مربیگری",
"account.settings.coaching.consent.decline-coaching": "ترجیح می‌دهم با خدمات مربیگری رایگان تماس نگیرم",
"account.settings.coaching.consent.label.name": "لطفا نام خود را تایید کنید",
"account.settings.coaching.consent.label.phone-number": "شماره تلفن همراه خود را وارد کنید",
"account.settings.coaching.consent.success.header": "موفق شدید!",
"account.settings.coaching.consent.success.message": "شما برای مربیگری ثبت‌نام کردید. در روزهای آینده منتظر پیامک یا رایانامه باشید.",
"account.settings.coaching.consent.success.continue": "آغاز دوره آموزشی ",
"account.settings.coaching.managed.support": "پشتیبانی",
"account.settings.coaching.managed.alert": "نام شما توسط {managerTitle} مدیریت می‌شود. برای راهنمایی با مدیر خود تماس بگیرید.",
"account.settings.field.phone_number": "شماره تلفن",
"account.settings.field.phone_number.empty": "افزودن شماره تلفن",
"account.settings.field.coaching_consent": "رضایت مربیگری",
"account.settings.field.coaching_consent.tooltip": "برنامه های MicroBachelors شامل مربیگری مبتنی بر پیام متنی است که به شما کمک می‌کند تجربیات آموزشی را با اهداف شغلی خود از طریق مشاوره یک به یک، جفت کنید. خدمات مربیگری بدون هزینه اضافی گنجانده شده است و برای زبان آموزان با شماره تلفن همراه ایالات متحده در دسترس است. نرخ استاندارد پیام‌رسانی اعمال می‌شود. برای انصراف از پیام‌ها، در هر زمان «توقف» را پیامک کنید.",
"account.settings.field.coaching_consent.error": "ارائه یک شماره تلفن معتبر ایالات متحده برای شرکت در مربیگری ضروری است",
"account.settings.delete.account.before.proceeding": "پیش از ادامه، لطفاً {actionLink}.",
"account.settings.delete.account.header": "حذف حساب کاربری من",
"account.settings.delete.account.subheader": "از رفتن شما متأسفیم. ",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Nom complet",
"account.settings.field.name.modal.certificate.option.verified": "Nom vérifié",
"account.settings.field.name.modal.certificate.button.choose": "Choisissez un nom",
"account.settings.coaching.consent.welcome.header": "Commençons.",
"account.settings.coaching.consent.welcome.subheader": "Nous sommes là pour vous du début à la fin",
"account.settings.coaching.consent.description": "Les programmes MicroBachelors comprennent un coaching axé sur votre carrière, vos études et la façon dont vous obtiendrez des résultats grâce à une communication individuelle avec un professionnel expérimenté. Si vous êtes intéressé, fournissez les informations ci-dessous et cliquez sur \"Soumettre\". Notre partenaire de coaching vous contactera par courrier électronique et/ou par SMS pour vous aider à progresser. Les conditions générales s'appliquent.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Les services de coaching sont inclus sans frais supplémentaires pour les apprenants ayant un numéro de téléphone aux États-Unis. Le coaching comprend des messages textes récurrents. Les tarifs des messages et des données peuvent s'appliquer. Text STOP pour se désengager.",
"account.settings.coaching.consent.accept-coaching": "S'inscrire pour le coaching.",
"account.settings.coaching.consent.decline-coaching": "Je préfère ne pas être contacté par les services de coaching gratuit.",
"account.settings.coaching.consent.label.name": "Veuillez confirmer votre nom",
"account.settings.coaching.consent.label.phone-number": "Entrer un numéro de cellulaire",
"account.settings.coaching.consent.success.header": "Opération réussie!",
"account.settings.coaching.consent.success.message": "Vous êtes inscrit au coaching. Vous pouvez vous attendre à un message par courriel ou SMS dans les prochains jours.",
"account.settings.coaching.consent.success.continue": "Démarrer mon cours",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Votre nom est géré par {managerTitle}. Contactez votre administrateur pour obtenir de l'aide.",
"account.settings.field.phone_number": "Numéro de téléphone",
"account.settings.field.phone_number.empty": "Ajouter un numéro de téléphone",
"account.settings.field.coaching_consent": "Consentement pour le coaching",
"account.settings.field.coaching_consent.tooltip": "Un parcours MicroBachelors inclus du coaching par texto avec un professionnel expériementé qui vous aidera à combiner votre expérience éducationnelle et vos objectifs de carrière. Les services de coaching sont inclus sans coût additionnel aux apprenant ayant un numéro de téléphone US. Le coût standard des textos s'applique. Texter 'STOP' en tout temp pour arrêter les messages.",
"account.settings.field.coaching_consent.error": "Un numéro de téléphone US valide est requis pour s'inscrire pour du coaching",
"account.settings.delete.account.before.proceeding": "Avant de poursuivre, veuillez {actionLink}.",
"account.settings.delete.account.header": "Supprimer mon compte",
"account.settings.delete.account.subheader": "Nous sommes désolés de vous voir quitter!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Nom complet",
"account.settings.field.name.modal.certificate.option.verified": "Nom vérifié",
"account.settings.field.name.modal.certificate.button.choose": "Choisissez un nom",
"account.settings.coaching.consent.welcome.header": "Commençons.",
"account.settings.coaching.consent.welcome.subheader": "Nous vous accompagnons du début jusqu'à la fin",
"account.settings.coaching.consent.description": "Un parcours MicroBachelors inclus du coaching avec un professionnel expérimenté qui vous guidera sur votre carrière, votre éducation et comment atteindre vos objectifs. Si vous êtes intéressés, entrez les informations requises ci-dessous et cliquez \"Soumettre\". Un guide vous contactera par courriel ou texto pour vous aider à progresser. Termes et conditions s'appliquent. *",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Nos services de coaching sont inclus sans coût additionnel aux apprenants ayant un numéro de téléphone US. Le coaching comprend des textos récurrents. Le coût des messages et des données peuvent s'appliquer. Envoyez STOP pour arrêter.",
"account.settings.coaching.consent.accept-coaching": "S'inscrire pour le coaching",
"account.settings.coaching.consent.decline-coaching": "Je préfère ne pas être contacté par les services de coaching gratuit",
"account.settings.coaching.consent.label.name": "Veuillez confirmer votre nom",
"account.settings.coaching.consent.label.phone-number": "Entrer un numéro de cellulaire",
"account.settings.coaching.consent.success.header": "Succès!",
"account.settings.coaching.consent.success.message": "Vous êtes inscrit au coaching. Vous pouvez vous attendre à un message par courriel ou SMS dans les prochains jours.",
"account.settings.coaching.consent.success.continue": "Commencer mon cours",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Votre nom est géré par {managerTitle}. Contactez votre administrateur pour obtenir de l'aide.",
"account.settings.field.phone_number": "Numéro de téléphone",
"account.settings.field.phone_number.empty": "Ajouter un numéro de téléphone",
"account.settings.field.coaching_consent": "Consentement pour le coaching",
"account.settings.field.coaching_consent.tooltip": "Un parcours MicroBachelors inclus du coaching par texto avec un professionnel expérimenté qui vous aidera à combiner votre expérience éducationnelle et vos objectifs de carrière. Les services de coaching sont inclus sans coût additionnel aux apprenants ayant un numéro de téléphone US. Le coût standard des textos s'applique. Envoyez 'STOP' en tout temps pour arrêter les messages.",
"account.settings.field.coaching_consent.error": "Un numéro de téléphone US valide est requis pour s'inscrire pour du coaching",
"account.settings.delete.account.before.proceeding": "Avant de continuer, veuillez {actionLink}.",
"account.settings.delete.account.header": "Supprimer mon compte",
"account.settings.delete.account.subheader": "Nous sommes désolés de vous voir quitter!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Nome e Cognome",
"account.settings.field.name.modal.certificate.option.verified": "Nome verificato",
"account.settings.field.name.modal.certificate.button.choose": "Scegli il nome",
"account.settings.coaching.consent.welcome.header": "Partiamo!",
"account.settings.coaching.consent.welcome.subheader": "Saremo qui per te dall'inizio alla fine",
"account.settings.coaching.consent.description": "I programmi MicroBachelors prevedono un servizio di coaching con un professionista esperto incentrato sulla tua carriera, istruzione e su come ottenere risultati attraverso la comunicazione individuale. Se sei interessato, fornisci le informazioni richieste di seguito e fai clic su \"Invia\" e il nostro partner di insegnamento si metterà in contatto con te tramite e-mail e/o messaggi sms per aiutarti a procedere. Si applicano Termini e Condizioni.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* I servizi di coaching sono inclusi senza costi aggiuntivi per gli studenti con numeri di telefono statunitensi. Il coaching include messaggi di testo ricorrenti. Potrebbero essere applicate tariffe per messaggi e dati. Invia un messaggio con il testo STOP per disattivare l'offerta.",
"account.settings.coaching.consent.accept-coaching": "Iscriviti al coaching",
"account.settings.coaching.consent.decline-coaching": "Preferisco non essere contattato con servizi di coaching gratuiti ",
"account.settings.coaching.consent.label.name": "Conferma il tuo nome",
"account.settings.coaching.consent.label.phone-number": "Immetti il tuo numero di cellulare",
"account.settings.coaching.consent.success.header": "Completato correttamente!",
"account.settings.coaching.consent.success.message": "Sei iscritto al coaching. Puoi aspettarti un messaggio via e-mail o SMS nei prossimi giorni. ",
"account.settings.coaching.consent.success.continue": "Inizia il mio corso",
"account.settings.coaching.managed.support": "supporto",
"account.settings.coaching.managed.alert": "Il tuo nome è gestito da {managerTitle}. Rivolgiti all'amministratore per assistenza.",
"account.settings.field.phone_number": "Numero di telefono",
"account.settings.field.phone_number.empty": "Aggiungi un numero di telefono",
"account.settings.field.coaching_consent": "Autorizza Coaching",
"account.settings.field.coaching_consent.tooltip": "I programmi MicroBachelors includono un servizio di coaching basato su messaggi SMS che ti aiuta ad abbinare le esperienze formative ai tuoi obiettivi di carriera attraverso consigli personali. I servizi di coaching sono inclusi senza costi aggiuntivi e sono disponibili per gli studenti con numeri di telefono cellulare statunitensi. Si applicano le tariffe standard per la messaggistica SMS. Invia in qualsiasi momento un SMSM con scritto STOP per disattivare i messaggi.",
"account.settings.field.coaching_consent.error": "Per attivare il servizio di coaching è necessario un numero di telefono statunitense valido",
"account.settings.delete.account.before.proceeding": "Prima di procedere, {actionLink}.",
"account.settings.delete.account.header": "Elimina il mio Account",
"account.settings.delete.account.subheader": "Ci dispiace che tu ci lasci!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Nome Completo",
"account.settings.field.name.modal.certificate.option.verified": "Nome Verificado",
"account.settings.field.name.modal.certificate.button.choose": "Escolha um nome",
"account.settings.coaching.consent.welcome.header": "Vamos começar.",
"account.settings.coaching.consent.welcome.subheader": "Estamos aqui para si do princípio ao fim",
"account.settings.coaching.consent.description": "Os programas MicroBachelors incluem formação que se concentra na sua carreira, educação, e como alcançará resultados através da comunicação personalizada com um profissional experiente. Se estiver interessado, forneça as informações abaixo e clique em \"Submeter\", e o nosso parceiro de formação ligar-se-á a si através de e-mail e/ou mensagem de texto para o ajudar a seguir em frente. Aplicam-se os termos e condições.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Os serviços de formação estão incluídos, sem custos adicionais para os alunos com números de telefone dos EUA. A formação inclui mensagens de texto recorrentes. Podem aplicar-se taxas de mensagens e dados. Digite STOP para optar por não participar .",
"account.settings.coaching.consent.accept-coaching": "Inscrever-se para a formação",
"account.settings.coaching.consent.decline-coaching": "Prefiro não ser contactado com serviços gratuitos de formação",
"account.settings.coaching.consent.label.name": "Por favor confirme o seu nome",
"account.settings.coaching.consent.label.phone-number": "Introduza o seu número de telemóvel",
"account.settings.coaching.consent.success.header": "Sucesso!",
"account.settings.coaching.consent.success.message": "Está inscrito como formador. Pode esperar uma mensagem via e-mail ou SMS nos próximos dias.",
"account.settings.coaching.consent.success.continue": "Iniciar o meu curso",
"account.settings.coaching.managed.support": "suporte",
"account.settings.coaching.managed.alert": "O seu nome é gerido por {managerTitle}. Contacte o seu administrador para obter ajuda.",
"account.settings.field.phone_number": "Telefone",
"account.settings.field.phone_number.empty": "Adicione um número de telefone",
"account.settings.field.coaching_consent": "Autorização para formação",
"account.settings.field.coaching_consent.tooltip": "Os programas MicroBachelors incluem treino baseado em mensagens de texto que o ajudam a associar experiências educacionais com os seus objectivos de carreira através de conselhos personalizados. Os serviços de formação estão incluídos sem custos adicionais, e estão disponíveis para estudantes com números de telemóvel nos EUA. Aplicam-se as taxas normais de mensagens. Escreva 'STOP' em qualquer altura para optar por não receber mensagens.",
"account.settings.field.coaching_consent.error": "É necessário um número de telefone válido nos EUA para optar pela formação",
"account.settings.delete.account.before.proceeding": "Antes de prosseguir, por favor {actionLink}.",
"account.settings.delete.account.header": "Eliminar a Minha Conta",
"account.settings.delete.account.subheader": "Lamentamos vê-lo/a partir!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "Full Name",
"account.settings.field.name.modal.certificate.option.verified": "Verified Name",
"account.settings.field.name.modal.certificate.button.choose": "Choose name",
"account.settings.coaching.consent.welcome.header": "Lets get started.",
"account.settings.coaching.consent.welcome.subheader": "We're here for you from start to finish",
"account.settings.coaching.consent.description": "MicroBachelors programs include coaching that focuses on your career, education, and how you'll achieve results through one-on-one communication with an experienced professional. If youre interested, provide the information below and click “Submit,” and our coaching partner will connect with you via email and/or text message to help you move forward. Terms and conditions apply.*",
"account.settings.coaching.consent.text-messaging.disclaimer": "* Coaching services are included at no additional cost to learners with US phone numbers. Coaching includes recurring text messages. Message and data rates may apply. Text STOP to opt-out.",
"account.settings.coaching.consent.accept-coaching": "Sign up for coaching",
"account.settings.coaching.consent.decline-coaching": "I prefer not to be contacted with free coaching services",
"account.settings.coaching.consent.label.name": "Please confirm your name",
"account.settings.coaching.consent.label.phone-number": "Enter your mobile number",
"account.settings.coaching.consent.success.header": "Success!",
"account.settings.coaching.consent.success.message": "You're signed up for coaching. You can expect a message via email or SMS in the coming days.",
"account.settings.coaching.consent.success.continue": "Start my course",
"account.settings.coaching.managed.support": "support",
"account.settings.coaching.managed.alert": "Your name is managed by {managerTitle}. Contact your administrator for help.",
"account.settings.field.phone_number": "Phone Number",
"account.settings.field.phone_number.empty": "Add a phone number",
"account.settings.field.coaching_consent": "Coaching consent",
"account.settings.field.coaching_consent.tooltip": "MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available to learners with U.S. mobile phone numbers. Standard messaging rates apply. Text STOP at anytime to opt-out of messages.",
"account.settings.field.coaching_consent.error": "A valid US phone number is required to opt into coaching",
"account.settings.delete.account.before.proceeding": "Before proceeding, please {actionLink}.",
"account.settings.delete.account.header": "Delete My Account",
"account.settings.delete.account.subheader": "We're sorry to see you go!",

View File

@@ -121,24 +121,6 @@
"account.settings.field.name.modal.certificate.option.full": "全名",
"account.settings.field.name.modal.certificate.option.verified": "验证名称",
"account.settings.field.name.modal.certificate.button.choose": "选择名字",
"account.settings.coaching.consent.welcome.header": "让我们开始吧。",
"account.settings.coaching.consent.welcome.subheader": "这里全程为您服务",
"account.settings.coaching.consent.description": "微型学士项目系统专注于您的职业、教育以及您将如何通过与经验丰富的专业人士的一对一交流取得成果的辅导。如果您有兴趣,请提供以下信息并单击“提交”,我们的讲师/助教将通过电子邮件与您联系,帮助您学习。条款与条件适用。",
"account.settings.coaching.consent.text-messaging.disclaimer": "拥有美国手机号码的用户无需支付辅导服务。辅导包括短信。您可能需要支付短信和数据使用费。发送stop终止服务。",
"account.settings.coaching.consent.accept-coaching": "报名参加辅导",
"account.settings.coaching.consent.decline-coaching": "我希望有人联系我免费提供辅导服务。",
"account.settings.coaching.consent.label.name": "请确认您的名字",
"account.settings.coaching.consent.label.phone-number": "请输入您的手机号码",
"account.settings.coaching.consent.success.header": "成功!",
"account.settings.coaching.consent.success.message": "您已报名参加辅导。您可以期待在未来几天通过电子邮件收到消息。",
"account.settings.coaching.consent.success.continue": "开始我的课程",
"account.settings.coaching.managed.support": "支持",
"account.settings.coaching.managed.alert": "您的名字由 {managerTitle} 管理。请联系您的管理员寻求帮助。",
"account.settings.field.phone_number": "电话号码",
"account.settings.field.phone_number.empty": "添加电话号码",
"account.settings.field.coaching_consent": "辅导同意",
"account.settings.field.coaching_consent.tooltip": "微型学士项目基于你的情况辅导通过一对一的建议帮助您将教育经历与您的职业目标相结合。提供学习计划。辅导项目对于持有美国移动号码的学生是免费的只收取标准短信费用。发送“stop”停止接收信息。",
"account.settings.field.coaching_consent.error": "*需要提供有效的美国号码接收辅导服务",
"account.settings.delete.account.before.proceeding": "再进行下一步之前,请{actionLink}。",
"account.settings.delete.account.header": "删除我的账号",
"account.settings.delete.account.subheader": "很遗憾看到您要离开了!",

View File

@@ -16,7 +16,6 @@ import Footer from '@edx/frontend-component-footer';
import configureStore from './data/configureStore';
import AccountSettingsPage, { NotFoundPage } from './account-settings';
import IdVerificationPage from './id-verification';
import CoachingConsent from './account-settings/coaching/CoachingConsent';
import messages from './i18n';
import './index.scss';
@@ -29,11 +28,10 @@ subscribe(APP_READY, () => {
<AppProvider store={configureStore()}>
<Head />
<Routes>
<Route path="/coaching_consent" element={<CoachingConsent />} />
<Route element={(
<div className="d-flex flex-column" style={{ minHeight: '100vh' }}>
<Header />
<main className="flex-grow-1">
<main className="flex-grow-1" id="main">
<Outlet />
</main>
<Footer />
@@ -65,7 +63,6 @@ initialize({
config: () => {
mergeConfig({
SUPPORT_URL: process.env.SUPPORT_URL,
COACHING_ENABLED: (process.env.COACHING_ENABLED || false),
ENABLE_DEMOGRAPHICS_COLLECTION: (process.env.ENABLE_DEMOGRAPHICS_COLLECTION || false),
DEMOGRAPHICS_BASE_URL: process.env.DEMOGRAPHICS_BASE_URL,
ENABLE_COPPA_COMPLIANCE: (process.env.ENABLE_COPPA_COMPLIANCE || false),

View File

@@ -38,21 +38,6 @@ $fa-font-path: "~font-awesome/fonts";
}
}
.coaching-header {
.logo {
display: block;
box-sizing: content-box;
height: 1.75rem;
padding: .75rem 0;
}
}
.coaching-consent {
.disclaimer {
font-size: 0.75rem;
}
}
.checkboxOption {
input:focus {
outline: -webkit-focus-ring-color auto 5px;

View File

@@ -11,8 +11,10 @@ import {
selectPreference,
selectPreferenceNonEditableChannels,
selectSelectedCourseId,
selectNotificationPreferencesStatus,
} from './data/selectors';
import { updatePreferenceToggle } from './data/thunks';
import { LOADING_STATUS } from '../constants';
const NotificationPreferenceRow = ({ appId, preferenceName }) => {
const dispatch = useDispatch();
@@ -20,6 +22,7 @@ const NotificationPreferenceRow = ({ appId, preferenceName }) => {
const courseId = useSelector(selectSelectedCourseId());
const preference = useSelector(selectPreference(appId, preferenceName));
const nonEditable = useSelector(selectPreferenceNonEditableChannels(appId, preferenceName));
const preferencesStatus = useSelector(selectNotificationPreferencesStatus());
const onToggle = useCallback((event) => {
const {
@@ -73,7 +76,7 @@ const NotificationPreferenceRow = ({ appId, preferenceName }) => {
name={channel}
value={preference[channel]}
onChange={onToggle}
disabled={nonEditable.includes(channel)}
disabled={nonEditable.includes(channel) || preferencesStatus === LOADING_STATUS}
/>
</div>
))}

View File

@@ -102,6 +102,7 @@ const notificationPreferencesReducer = (state = defaultState, action = {}) => {
? { ...preference, [notificationChannel]: value }
: preference
)),
status: LOADING_STATUS,
},
};
case Actions.UPDATE_APP_PREFERENCE:

View File

@@ -126,7 +126,7 @@ export const updatePreferenceToggle = (
notificationApp,
notificationType,
notificationChannel,
value,
!value,
));
const data = await patchPreferenceToggle(
courseId,