Adding frontend-base (#120)
* Use the new header and footer. Note: Because we’re not fully using frontend-base yet, the header is broken. It’ll start working once frontend-base’s App singleton is properly initialized. * Initializing the app via App.initialize - Removes App component - Cleans up environment configuration - SUPPORT_URL is the only custom env variable. - Cleans up usage of SUPPORT_URL and LOGOUT_URL to take advantage of App.config. * Convert delete-account service to use App. * Using App for services and cleaning up associated code. Also pulling out the frontend-auth shim, since it was also dead and was sorta API-service like. * Cleaning up “common” and some dead code. - Most of it goes into account-settings for now. - Shuffling the “utils” files around to classify them better. - Removing unused assets * Moving files into data subdirectory in account-settings Including all the utils stuff. * Moving top level reducers/sagas into a data dir * Fix import bug with sagaUtils * Removing connected-react-router * Ceasing to use authentication and configuration from redux Also removing some unnecessary test config. * Updating redux init to default to prod. Also fixing a bug where it wasn’t going into prod mode at all. * Moving the duplicateTpaProvider logic out of redux This lets us stop setting initial state on redux. Also removing url-polyfill. * A little cleanup. * Remove default exports to keep the pattern the same.
This commit is contained in:
19
.env
19
.env
@@ -1,4 +1,3 @@
|
||||
NODE_ENV=null
|
||||
ACCESS_TOKEN_COOKIE_NAME=null
|
||||
BASE_URL=null
|
||||
CREDENTIALS_BASE_URL=null
|
||||
@@ -9,24 +8,10 @@ LMS_BASE_URL=null
|
||||
LOGIN_URL=null
|
||||
LOGOUT_URL=null
|
||||
MARKETING_SITE_BASE_URL=null
|
||||
NODE_ENV=null
|
||||
ORDER_HISTORY_URL=null
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT=null
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
APPLE_APP_STORE_URL=null
|
||||
CONTACT_URL=null
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM=null
|
||||
ENTERPRISE_MARKETING_URL=null
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN=null
|
||||
ENTERPRISE_MARKETING_UTM_SOURCE=null
|
||||
FACEBOOK_URL=null
|
||||
GOOGLE_PLAY_URL=null
|
||||
LINKED_IN_URL=null
|
||||
OPEN_SOURCE_URL=null
|
||||
PRIVACY_POLICY_URL=null
|
||||
REDDIT_URL=null
|
||||
SUPPORT_URL=null
|
||||
TERMS_OF_SERVICE_URL=null
|
||||
TWITTER_URL=null
|
||||
YOU_TUBE_URL=null
|
||||
USER_INFO_COOKIE_NAME=null
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
NODE_ENV='development'
|
||||
PORT=1997
|
||||
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
|
||||
BASE_URL='localhost:1997'
|
||||
CREDENTIALS_BASE_URL='http://localhost:18150'
|
||||
@@ -10,24 +8,11 @@ LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/login'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
NODE_ENV='development'
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
PORT=1997
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
APPLE_APP_STORE_URL='https://www.apple.com/ios/app-store/'
|
||||
CONTACT_URL='http://localhost:18000/contact'
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM='Footer'
|
||||
ENTERPRISE_MARKETING_URL='http://example.com'
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN='my_campaign'
|
||||
ENTERPRISE_MARKETING_UTM_SOURCE='edX profile'
|
||||
FACEBOOK_URL='https://www.facebook.com'
|
||||
GOOGLE_PLAY_URL='https://play.google.com/store'
|
||||
LINKED_IN_URL='https://www.linkedin.com'
|
||||
OPEN_SOURCE_URL='http://localhost:18000/openedx'
|
||||
PRIVACY_POLICY_URL='http://localhost:18000/privacy-policy'
|
||||
REDDIT_URL='https://www.reddit.com'
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
TERMS_OF_SERVICE_URL='http://localhost:18000/terms-of-service'
|
||||
TWITTER_URL='https://twitter.com'
|
||||
YOU_TUBE_URL='https://www.youtube.com'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
|
||||
@@ -8,8 +8,10 @@ LMS_BASE_URL='http://localhost:18000'
|
||||
LOGIN_URL='http://localhost:18000/login'
|
||||
LOGOUT_URL='http://localhost:18000/login'
|
||||
MARKETING_SITE_BASE_URL='http://localhost:18000'
|
||||
NODE_ENV=null
|
||||
ORDER_HISTORY_URL='localhost:1996/orders'
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
|
||||
SEGMENT_KEY=null
|
||||
SITE_NAME='edX'
|
||||
SUPPORT_URL='http://localhost:18000/support'
|
||||
USER_INFO_COOKIE_NAME='edx-user-info'
|
||||
|
||||
237
package-lock.json
generated
237
package-lock.json
generated
@@ -979,9 +979,9 @@
|
||||
"integrity": "sha512-APBpZvdQrC1MJWMzk33V7FR2RhBRtnH2QPLqZzS+qia7PixwgWNlnX7UfHjhx+YWkM53GdsZKs40EBkSwADuMA=="
|
||||
},
|
||||
"@edx/frontend-analytics": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-analytics/-/frontend-analytics-2.0.0.tgz",
|
||||
"integrity": "sha512-dj01bUVFA0sIlxtMH1YhQrVjWdmmiUeDIR3QqSXAxU2vwX2F4bsYUm8t0kCyg+8CjfkTcuZvyHHfwvQwlFOx2g==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-analytics/-/frontend-analytics-3.0.0.tgz",
|
||||
"integrity": "sha512-+/4ILACcNBkscq4r+ZJvx/hsUqZu9c3Sqb4nm2TyAGqEKYSw010pE6Y4d6wi4tkXW6R6wc3VA2BnXdw5g7gsrA==",
|
||||
"requires": {
|
||||
"form-urlencoded": "^3.0.0",
|
||||
"lodash.snakecase": "^4.1.1"
|
||||
@@ -995,12 +995,12 @@
|
||||
}
|
||||
},
|
||||
"@edx/frontend-auth": {
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-5.3.5.tgz",
|
||||
"integrity": "sha512-zmd0yMQ5ex6bhRkA7FLbCgYfZG/F+NPScB85drtCp5cd2GLgjASYGH35s0GpCwAo7p4KcXFQ0uHmPONZro6qDA==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-auth/-/frontend-auth-7.0.1.tgz",
|
||||
"integrity": "sha512-oElicVn8fbSAPEc79ivObiYgRTv62aNXP8y+utGBHQCNDi6N7bA4hXcKLjvz25hExmlzJdIR4v5IOLZiVMB5VQ==",
|
||||
"requires": {
|
||||
"@edx/frontend-logging": "^2.0.1",
|
||||
"axios": "^0.18.0",
|
||||
"axios": "^0.18.1",
|
||||
"camelcase-keys": "^5.0.0",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"pubsub-js": "^1.7.0",
|
||||
@@ -1009,6 +1009,11 @@
|
||||
"url-parse": "^1.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@edx/frontend-logging": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-logging/-/frontend-logging-2.1.0.tgz",
|
||||
"integrity": "sha512-IN0Bgh0/1Ax3TMPfZztqzdJchW4B5Px9PT4V9uu6TMj2Cj8el1CV3jrSA4Idg8C3CAkFZ/EHjmaFVCxgJ9aXVA=="
|
||||
},
|
||||
"universal-cookie": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-3.1.0.tgz",
|
||||
@@ -1022,6 +1027,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@edx/frontend-base": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-base/-/frontend-base-4.1.0.tgz",
|
||||
"integrity": "sha512-jGHCsQ4um6sWrh3L0ZJm+tBmmtGWefS1ULR9PNi6sDrXgKa1gceBg2W/uYzez0glMtS7HRn5zJ1kRpJHbDW7aA==",
|
||||
"requires": {
|
||||
"babel-polyfill": "6.26.0",
|
||||
"history": "4.9.0",
|
||||
"lodash.memoize": "4.1.2",
|
||||
"lodash.merge": "4.6.2",
|
||||
"pubsub-js": "1.7.0",
|
||||
"redux-devtools-extension": "2.13.8",
|
||||
"redux-logger": "3.0.6",
|
||||
"redux-thunk": "2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"history": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz",
|
||||
"integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"loose-envify": "^1.2.0",
|
||||
"resolve-pathname": "^2.2.0",
|
||||
"tiny-invariant": "^1.0.2",
|
||||
"tiny-warning": "^1.0.0",
|
||||
"value-equal": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"resolve-pathname": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz",
|
||||
"integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg=="
|
||||
},
|
||||
"value-equal": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz",
|
||||
"integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@edx/frontend-build": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-build/-/frontend-build-1.2.2.tgz",
|
||||
@@ -1079,53 +1124,79 @@
|
||||
}
|
||||
},
|
||||
"@edx/frontend-component-footer": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-6.0.2.tgz",
|
||||
"integrity": "sha512-TB9fc85uXLRbznjshMNYJcLcwOA1GgPxaRrga8qpybb/80p4fawZnBY1XsupHFs15j/Xltk6xvF+QIm26K7m7A==",
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-footer/-/frontend-component-footer-9.0.1.tgz",
|
||||
"integrity": "sha512-lAiffuU95XDP/XrlrZd+mW70wQHQTWWD0BX7xjoy4s8hTKNyoOL1b8zAvwhlXFeHkcfNjn/Z3iNnhgKu8Y1SgA==",
|
||||
"requires": {
|
||||
"query-string": "^5.1.1"
|
||||
}
|
||||
},
|
||||
"@edx/frontend-component-site-header": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-site-header/-/frontend-component-site-header-2.4.0.tgz",
|
||||
"integrity": "sha512-Z1JICIJxG6kcpBck7YxEubPZyiBGS2z0SrRJXX7JcRRpKjDNDcooOQYG/SiZt5OMVVwOOeJdQDxhMtu3zT+fSQ==",
|
||||
"requires": {
|
||||
"react-responsive": "^6.1.1",
|
||||
"react-transition-group": "^2.5.2"
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.17",
|
||||
"@fortawesome/free-brands-svg-icons": "5.8.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.8.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.8.1",
|
||||
"@fortawesome/react-fontawesome": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-transition-group": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
|
||||
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
|
||||
"@fortawesome/fontawesome-svg-core": {
|
||||
"version": "1.2.17",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.17.tgz",
|
||||
"integrity": "sha512-TORMW/wIX2QyyGBd4XwHGPir4/0U18Wxf+iDBAUW3EIJ0/VC/ZMpJOiyiCe1f8g9h0PPzA7sqVtl8JtTUtm4uA==",
|
||||
"requires": {
|
||||
"dom-helpers": "^3.4.0",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
"@fortawesome/fontawesome-common-types": "^0.2.17"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-brands-svg-icons": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.8.1.tgz",
|
||||
"integrity": "sha512-NN5Nap2D5e7Lusa5uarAUkcaO7PMbme5wmUF8kofZzPUZR753zDg/UFffi+LLE2Mi9zRXCJEYmIRfMON9SxLPg==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "^0.2.17"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-regular-svg-icons": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.8.1.tgz",
|
||||
"integrity": "sha512-U+tFjDyQpVdD0UPWoKRBVLhh0J1/q3iaWDrnxNMJKuKRmerc4d0jfiZdM2X7agOTcG7amvcllRBiWCu2FwYlMA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "^0.2.17"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@edx/frontend-component-header": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-component-header/-/frontend-component-header-1.1.4.tgz",
|
||||
"integrity": "sha512-6Mt2Q+VKdwI1PlhpJ8OE0/975fbPnOXgvLwkzoDIrYkd5OTP+VjEB3uoyqJtDENkH7d8XF1T09WJJCaTPQieUQ==",
|
||||
"requires": {
|
||||
"babel-polyfill": "6.26.0",
|
||||
"react-responsive": "8.0.1",
|
||||
"react-transition-group": "4.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-responsive": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-8.0.1.tgz",
|
||||
"integrity": "sha512-caseFCvFFV4QW+JOl7inzDme+avoX4r7GPpQJ+04NCzIgbroV3BU0noPgHGxVCEFKm9IsgcKOBAf+6MqIUeQIg==",
|
||||
"requires": {
|
||||
"hyphenate-style-name": "^1.0.0",
|
||||
"matchmediaquery": "^0.3.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"shallow-equal": "^1.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@edx/frontend-i18n": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-i18n/-/frontend-i18n-2.1.0.tgz",
|
||||
"integrity": "sha512-TmaxNNAFFRWT0EkRxy7gQrnnIzJuFn+HO8HOaYoO4vWJhvvfr++G+0ysgyNLuxms9CdFB1LuB64yEGupFSqcVA==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-i18n/-/frontend-i18n-3.0.3.tgz",
|
||||
"integrity": "sha512-1oBPba9xd/GK0hSBPhTdNGtHA+76j8sWJxRD9gQ+GsHLHF7Z75GsX+Ls6pB+fTNi9tjqaVWQLsA9rMGcj92hqQ==",
|
||||
"requires": {
|
||||
"@cospired/i18n-iso-languages": "^2.0.2",
|
||||
"glob": "^7.1.4",
|
||||
"i18n-iso-countries": "^4.0.0",
|
||||
"iso-countries-languages": "^0.2.1",
|
||||
"react-intl": "^2.9.0",
|
||||
"universal-cookie": "^4.0.0"
|
||||
"@cospired/i18n-iso-languages": "2.0.2",
|
||||
"glob": "7.1.4",
|
||||
"i18n-iso-countries": "4.0.0",
|
||||
"iso-countries-languages": "0.3.0",
|
||||
"react-intl": "2.9.0",
|
||||
"universal-cookie": "4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"i18n-iso-countries": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-4.1.0.tgz",
|
||||
"integrity": "sha512-ttqCFBUvVSwUCgyjjIG95lilFg/61INah89ih/znBYHrZAcD5HsFUr8CJBmEgIOPbw0jZFgAPAsYRPGQexMTeA=="
|
||||
},
|
||||
"universal-cookie": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.0.tgz",
|
||||
@@ -1140,9 +1211,9 @@
|
||||
}
|
||||
},
|
||||
"@edx/frontend-logging": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-logging/-/frontend-logging-2.0.2.tgz",
|
||||
"integrity": "sha512-DFj4CXcy5jStwkEN/sBiTFV5IB06KnTdWIfk/OYpx7EnMJCXL7b8BawW74xstyVjkGXRhQr08sgt/cY+hNGd7A=="
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@edx/frontend-logging/-/frontend-logging-3.0.1.tgz",
|
||||
"integrity": "sha512-kRDsPbTUxNfZdnC4KN5HratS/7bkCYv/gyvUnBcuPbiONXwSuriNIVAKCepldvhg1DTwLqQMXh+Qw6vo2r048A=="
|
||||
},
|
||||
"@edx/paragon": {
|
||||
"version": "7.1.5",
|
||||
@@ -1896,9 +1967,9 @@
|
||||
}
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.2.tgz",
|
||||
"integrity": "sha512-aHQA072E10/8iUQsPH7mQU/KUyQBZAGzTVRCUvnSz8mSvbrYsP4xEO2RSA0Pjltolzi0j8+8ixrm//Hr4umPzw=="
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz",
|
||||
"integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow=="
|
||||
},
|
||||
"@types/events": {
|
||||
"version": "3.0.0",
|
||||
@@ -2803,9 +2874,9 @@
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
|
||||
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
|
||||
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
@@ -4479,16 +4550,6 @@
|
||||
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
||||
"dev": true
|
||||
},
|
||||
"connected-react-router": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-6.5.2.tgz",
|
||||
"integrity": "sha512-qzsLPZCofSI80fwy+HgxtEgSGS4ndYUUZAWaw1dqaOGPLKX/FVwIOEb7q+hjHdnZ4v5pKZcNv5GG4urjujIoyA==",
|
||||
"requires": {
|
||||
"immutable": "^3.8.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"seamless-immutable": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
@@ -5086,7 +5147,8 @@
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
||||
"dev": true
|
||||
},
|
||||
"decompress": {
|
||||
"version": "4.2.0",
|
||||
@@ -5514,14 +5576,6 @@
|
||||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"dom-helpers": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
|
||||
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2"
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||
@@ -9473,6 +9527,11 @@
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
|
||||
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
|
||||
},
|
||||
"i18n-iso-countries": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-4.0.0.tgz",
|
||||
"integrity": "sha512-DL3U2/rW4EJKXqNofuB/mHfxaca/0gwoGc7mJblvvjkNadlfWzaPaQHtC0/Cabg4SCM9+zJpl80trKx3dRY9VQ=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -9691,11 +9750,6 @@
|
||||
"integrity": "sha512-O3sR1/opvCDGLEVcvrGTMtLac8GJ5IwZC4puPrLuRj3l7ICKvkmA0vGuU9OW8mV9WIBRnaxp5GJh9IEAaNOoYg==",
|
||||
"dev": true
|
||||
},
|
||||
"immutable": {
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz",
|
||||
"integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM="
|
||||
},
|
||||
"import-cwd": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
|
||||
@@ -10336,9 +10390,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"iso-countries-languages": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/iso-countries-languages/-/iso-countries-languages-0.2.1.tgz",
|
||||
"integrity": "sha512-MSLwTToJmw0VS/NdCvwsgt28zE5A/rGsPpdPOrepFgAanVDBUguAgGj/73NTIrLifICz2pTmJNHZNPwDXmAMTw=="
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/iso-countries-languages/-/iso-countries-languages-0.3.0.tgz",
|
||||
"integrity": "sha512-DFiVhxSc9yJtk1FgBF07kafc7Np8otbDgjDsmM5xcljn8evda6izPmTrhE8b09+uoPGm/IEF1t4E/gHm03yAjw=="
|
||||
},
|
||||
"isobject": {
|
||||
"version": "3.0.1",
|
||||
@@ -11276,14 +11330,12 @@
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
|
||||
"dev": true
|
||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
||||
},
|
||||
"lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
|
||||
},
|
||||
"lodash.mergewith": {
|
||||
"version": "4.6.2",
|
||||
@@ -14323,6 +14375,7 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
|
||||
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"object-assign": "^4.1.0",
|
||||
@@ -14566,11 +14619,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",
|
||||
"integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA=="
|
||||
},
|
||||
"react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-proptype-conditional-require": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz",
|
||||
@@ -15884,11 +15932,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"seamless-immutable": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/seamless-immutable/-/seamless-immutable-7.1.4.tgz",
|
||||
"integrity": "sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A=="
|
||||
},
|
||||
"seek-bzip": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz",
|
||||
@@ -16138,6 +16181,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"shallow-equal": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-1.2.0.tgz",
|
||||
"integrity": "sha512-Z21pVxR4cXsfwpMKMhCEIO1PCi5sp7KEp+CmOpBQ+E8GpHwKOw2sEzk7sgblM3d/j4z4gakoWEoPcjK0VJQogA=="
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||
@@ -18194,11 +18242,6 @@
|
||||
"prepend-http": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"url-polyfill": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.7.tgz",
|
||||
"integrity": "sha512-ZrAxYWCREjmMtL8gSbSiKKLZZticgihCvVBtrFbUVpyoETt8GQJeG2okMWA8XryDAaHMjJfhnc+rnhXRbI4DXA=="
|
||||
},
|
||||
"url-to-options": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
|
||||
|
||||
18
package.json
18
package.json
@@ -29,12 +29,13 @@
|
||||
"ie 11"
|
||||
],
|
||||
"dependencies": {
|
||||
"@edx/frontend-analytics": "^2.0.0",
|
||||
"@edx/frontend-auth": "^5.3.5",
|
||||
"@edx/frontend-component-footer": "^6.0.2",
|
||||
"@edx/frontend-component-site-header": "^2.4.0",
|
||||
"@edx/frontend-i18n": "^2.1.0",
|
||||
"@edx/frontend-logging": "^2.0.2",
|
||||
"@edx/frontend-analytics": "^3.0.0",
|
||||
"@edx/frontend-auth": "^7.0.1",
|
||||
"@edx/frontend-base": "^4.1.0",
|
||||
"@edx/frontend-component-footer": "^9.0.1",
|
||||
"@edx/frontend-component-header": "^1.1.4",
|
||||
"@edx/frontend-i18n": "^3.0.3",
|
||||
"@edx/frontend-logging": "^3.0.1",
|
||||
"@edx/paragon": "^7.1.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.18",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.8.2",
|
||||
@@ -43,7 +44,6 @@
|
||||
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"classnames": "^2.2.6",
|
||||
"connected-react-router": "^6.5.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"form-urlencoded": "^4.0.1",
|
||||
"formdata-polyfill": "^3.0.19",
|
||||
@@ -52,6 +52,7 @@
|
||||
"lodash.findindex": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isempty": "^4.4.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"lodash.pick": "^4.4.0",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
@@ -72,8 +73,7 @@
|
||||
"redux-saga": "^1.1.1",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"universal-cookie": "^4.0.2",
|
||||
"url-polyfill": "^1.1.7"
|
||||
"universal-cookie": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edx/frontend-build": "^1.2.2",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { AppContext, fetchUserAccount, App } from '@edx/frontend-base';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import memoize from 'memoize-one';
|
||||
import findIndex from 'lodash.findindex';
|
||||
import { sendTrackingLogEvent } from '@edx/frontend-analytics';
|
||||
import {
|
||||
injectIntl,
|
||||
intlShape,
|
||||
@@ -11,11 +13,10 @@ import {
|
||||
import { Hyperlink } from '@edx/paragon';
|
||||
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
|
||||
import { fetchSettings, saveSettings, updateDraft } from './actions';
|
||||
import { accountSettingsPageSelector } from './selectors';
|
||||
|
||||
import { Alert, PageLoading } from '../common';
|
||||
import { fetchSettings, saveSettings, updateDraft } from './data/actions';
|
||||
import { accountSettingsPageSelector } from './data/selectors';
|
||||
import PageLoading from './PageLoading';
|
||||
import Alert from './Alert';
|
||||
import JumpNav from './JumpNav';
|
||||
import DeleteAccount from './delete-account';
|
||||
import EditableField from './EditableField';
|
||||
@@ -27,7 +28,7 @@ import {
|
||||
YEAR_OF_BIRTH_OPTIONS,
|
||||
EDUCATION_LEVELS,
|
||||
GENDER_OPTIONS,
|
||||
} from './constants';
|
||||
} from './data/constants';
|
||||
import { fetchSiteLanguages } from './site-language';
|
||||
|
||||
class AccountSettingsPage extends React.Component {
|
||||
@@ -53,11 +54,29 @@ class AccountSettingsPage extends React.Component {
|
||||
value: '',
|
||||
label: props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
|
||||
}].concat(props.countryOptions);
|
||||
|
||||
// If there is a "duplicate_provider" query parameter, that's the backend's
|
||||
// way of telling us that the provider account the user tried to link is already linked
|
||||
// to another Open edX account. We use this to display a message to that effect, and remove the
|
||||
// parameter from the URL.
|
||||
const duplicateTpaProvider = App.queryParams.duplicate_provider;
|
||||
if (duplicateTpaProvider !== undefined) {
|
||||
App.history.replace(App.history.location.pathname);
|
||||
}
|
||||
this.state = {
|
||||
duplicateTpaProvider,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchUserAccount(this.context.authenticatedUser.username);
|
||||
this.props.fetchSettings();
|
||||
this.props.fetchSiteLanguages();
|
||||
sendTrackingLogEvent('edx.user.settings.viewed', {
|
||||
page: 'account',
|
||||
visibility: null,
|
||||
user_id: this.context.authenticatedUser.userId,
|
||||
});
|
||||
}
|
||||
|
||||
getTimeZoneOptions = memoize((timeZoneOptions, countryTimeZoneOptions) => {
|
||||
@@ -97,7 +116,7 @@ class AccountSettingsPage extends React.Component {
|
||||
};
|
||||
|
||||
renderDuplicateTpaProviderMessage() {
|
||||
if (!this.props.duplicateTpaProvider) {
|
||||
if (!this.state.duplicateTpaProvider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -109,7 +128,7 @@ class AccountSettingsPage extends React.Component {
|
||||
defaultMessage="The {provider} account you selected is already linked to another edX account."
|
||||
description="alert message informing the user that the third-party account they attempted to link is already linked to another edX account"
|
||||
values={{
|
||||
provider: <b>{this.props.duplicateTpaProvider}</b>,
|
||||
provider: <b>{this.state.duplicateTpaProvider}</b>,
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
@@ -132,7 +151,7 @@ class AccountSettingsPage extends React.Component {
|
||||
values={{
|
||||
managerTitle: <b>{this.props.profileDataManager}</b>,
|
||||
support: (
|
||||
<Hyperlink destination={this.props.supportUrl} target="_blank">
|
||||
<Hyperlink destination={App.config.SUPPORT_URL} target="_blank">
|
||||
<FormattedMessage
|
||||
id="account.settings.message.managed.settings.support"
|
||||
defaultMessage="support"
|
||||
@@ -366,7 +385,6 @@ class AccountSettingsPage extends React.Component {
|
||||
<DeleteAccount
|
||||
isVerifiedAccount={this.props.isActive}
|
||||
hasLinkedTPA={hasLinkedTPA}
|
||||
logoutUrl={this.props.logoutUrl}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -420,6 +438,8 @@ class AccountSettingsPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
AccountSettingsPage.contextType = AppContext;
|
||||
|
||||
AccountSettingsPage.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
loading: PropTypes.bool,
|
||||
@@ -472,14 +492,12 @@ AccountSettingsPage.propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
})),
|
||||
fetchUserAccount: PropTypes.func.isRequired,
|
||||
fetchSiteLanguages: PropTypes.func.isRequired,
|
||||
updateDraft: PropTypes.func.isRequired,
|
||||
saveSettings: PropTypes.func.isRequired,
|
||||
fetchSettings: PropTypes.func.isRequired,
|
||||
duplicateTpaProvider: PropTypes.string,
|
||||
tpaProviders: PropTypes.arrayOf(PropTypes.object),
|
||||
supportUrl: PropTypes.string.isRequired,
|
||||
logoutUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
AccountSettingsPage.defaultProps = {
|
||||
@@ -495,12 +513,12 @@ AccountSettingsPage.defaultProps = {
|
||||
profileDataManager: null,
|
||||
staticFields: [],
|
||||
hiddenFields: ['secondary_email'],
|
||||
duplicateTpaProvider: null,
|
||||
tpaProviders: [],
|
||||
isActive: true,
|
||||
};
|
||||
|
||||
export default connect(accountSettingsPageSelector, {
|
||||
fetchUserAccount,
|
||||
fetchSettings,
|
||||
saveSettings,
|
||||
updateDraft,
|
||||
|
||||
@@ -4,11 +4,11 @@ import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { connect } from 'react-redux';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
|
||||
import { betaLanguageBannerSelector } from './selectors';
|
||||
import { betaLanguageBannerSelector } from './data/selectors';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
import { saveSettings } from './actions';
|
||||
import { TRANSIFEX_LANGUAGE_BASE_URL } from './constants';
|
||||
import { Alert } from '../common';
|
||||
import { saveSettings } from './data/actions';
|
||||
import { TRANSIFEX_LANGUAGE_BASE_URL } from './data/constants';
|
||||
import Alert from './Alert';
|
||||
|
||||
class BetaLanguageBanner extends React.Component {
|
||||
getSiteLanguageEntry(languageCode) {
|
||||
|
||||
@@ -6,14 +6,14 @@ import { Button, Input, StatefulButton, ValidationFormGroup } from '@edx/paragon
|
||||
import { faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { SwitchContent } from '../common';
|
||||
import SwitchContent from './SwitchContent';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
|
||||
import {
|
||||
openForm,
|
||||
closeForm,
|
||||
} from './actions';
|
||||
import { editableFieldSelector } from './selectors';
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
|
||||
|
||||
function EditableField(props) {
|
||||
|
||||
@@ -6,14 +6,15 @@ import { Button, StatefulButton, Input, ValidationFormGroup } from '@edx/paragon
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Alert, SwitchContent } from '../common';
|
||||
import Alert from './Alert';
|
||||
import SwitchContent from './SwitchContent';
|
||||
import messages from './AccountSettingsPage.messages';
|
||||
|
||||
import {
|
||||
openForm,
|
||||
closeForm,
|
||||
} from './actions';
|
||||
import { editableFieldSelector } from './selectors';
|
||||
} from './data/actions';
|
||||
import { editableFieldSelector } from './data/selectors';
|
||||
|
||||
|
||||
function EmailField(props) {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { utils } from '../common';
|
||||
|
||||
const { AsyncActionType } = utils;
|
||||
import { AsyncActionType } from './utils';
|
||||
|
||||
export const FETCH_SETTINGS = new AsyncActionType('ACCOUNT_SETTINGS', 'FETCH_SETTINGS');
|
||||
export const SAVE_SETTINGS = new AsyncActionType('ACCOUNT_SETTINGS', 'SAVE_SETTINGS');
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
RESET_DRAFTS,
|
||||
} from './actions';
|
||||
|
||||
import { reducer as deleteAccountReducer, DELETE_ACCOUNT } from './delete-account';
|
||||
import { reducer as siteLanguageReducer, FETCH_SITE_LANGUAGES } from './site-language';
|
||||
import { reducer as resetPasswordReducer, RESET_PASSWORD } from './reset-password';
|
||||
import { reducer as thirdPartyAuthReducer, DISCONNECT_AUTH } from './third-party-auth';
|
||||
import { reducer as deleteAccountReducer, DELETE_ACCOUNT } from '../delete-account';
|
||||
import { reducer as siteLanguageReducer, FETCH_SITE_LANGUAGES } from '../site-language';
|
||||
import { reducer as resetPasswordReducer, RESET_PASSWORD } from '../reset-password';
|
||||
import { reducer as thirdPartyAuthReducer, DISCONNECT_AUTH } from '../third-party-auth';
|
||||
|
||||
export const defaultState = {
|
||||
loading: false,
|
||||
@@ -1,5 +1,8 @@
|
||||
import { call, put, delay, takeEvery, select, all } from 'redux-saga/effects';
|
||||
|
||||
import { App } from '@edx/frontend-base';
|
||||
import { setLocale, handleRtl } from '@edx/frontend-i18n';
|
||||
|
||||
// Actions
|
||||
import {
|
||||
FETCH_SETTINGS,
|
||||
@@ -16,29 +19,30 @@ import {
|
||||
fetchTimeZones,
|
||||
fetchTimeZonesSuccess,
|
||||
} from './actions';
|
||||
import { usernameSelector, userRolesSelector, siteLanguageSelector } from './selectors';
|
||||
import { siteLanguageSelector } from './selectors';
|
||||
|
||||
// Sub-modules
|
||||
import { saga as deleteAccountSaga } from './delete-account';
|
||||
import { saga as resetPasswordSaga } from './reset-password';
|
||||
import { saga as siteLanguageSaga, ApiService as SiteLanguageApiService } from './site-language';
|
||||
import { saga as thirdPartyAuthSaga } from './third-party-auth';
|
||||
import { saga as deleteAccountSaga } from '../delete-account';
|
||||
import { saga as resetPasswordSaga } from '../reset-password';
|
||||
import {
|
||||
saga as siteLanguageSaga,
|
||||
patchPreferences,
|
||||
postSetLang,
|
||||
} from '../site-language';
|
||||
import { saga as thirdPartyAuthSaga } from '../third-party-auth';
|
||||
|
||||
// Services
|
||||
import * as ApiService from './service';
|
||||
|
||||
import { setLocale, handleRtl } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
import { getSettings, patchSettings, getTimeZones } from './service';
|
||||
|
||||
export function* handleFetchSettings() {
|
||||
try {
|
||||
yield put(fetchSettingsBegin());
|
||||
const username = yield select(usernameSelector);
|
||||
const userRoles = yield select(userRolesSelector);
|
||||
const { username, roles: userRoles } = App.authenticatedUser;
|
||||
|
||||
const {
|
||||
thirdPartyAuthProviders, profileDataManager, timeZones, ...values
|
||||
} = yield call(
|
||||
ApiService.getSettings,
|
||||
getSettings,
|
||||
username,
|
||||
userRoles,
|
||||
);
|
||||
@@ -61,22 +65,22 @@ export function* handleSaveSettings(action) {
|
||||
try {
|
||||
yield put(saveSettingsBegin());
|
||||
|
||||
const username = yield select(usernameSelector);
|
||||
const { username } = App.authenticatedUser;
|
||||
const { commitValues, formId } = action.payload;
|
||||
const commitData = { [formId]: commitValues };
|
||||
let savedValues = null;
|
||||
if (formId === 'siteLanguage') {
|
||||
const previousSiteLanguage = yield select(siteLanguageSelector);
|
||||
yield all([
|
||||
call(SiteLanguageApiService.patchPreferences, username, { prefLang: commitValues }),
|
||||
call(SiteLanguageApiService.postSetLang, commitValues),
|
||||
call(patchPreferences, username, { prefLang: commitValues }),
|
||||
call(postSetLang, commitValues),
|
||||
]);
|
||||
yield put(setLocale(commitValues));
|
||||
yield put(savePreviousSiteLanguage(previousSiteLanguage.savedValue));
|
||||
handleRtl();
|
||||
savedValues = commitData;
|
||||
} else {
|
||||
savedValues = yield call(ApiService.patchSettings, username, commitData);
|
||||
savedValues = yield call(patchSettings, username, commitData);
|
||||
}
|
||||
yield put(saveSettingsSuccess(savedValues, commitData));
|
||||
if (savedValues.country) yield put(fetchTimeZones(savedValues.country));
|
||||
@@ -93,7 +97,7 @@ export function* handleSaveSettings(action) {
|
||||
}
|
||||
|
||||
export function* handleFetchTimeZones(action) {
|
||||
const response = yield call(ApiService.getTimeZones, action.payload.country);
|
||||
const response = yield call(getTimeZones, action.payload.country);
|
||||
yield put(fetchTimeZonesSuccess(response, action.payload.country));
|
||||
}
|
||||
|
||||
@@ -5,20 +5,12 @@ import {
|
||||
getLanguageList,
|
||||
} from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import { siteLanguageOptionsSelector, siteLanguageListSelector } from './site-language';
|
||||
import { siteLanguageOptionsSelector, siteLanguageListSelector } from '../site-language';
|
||||
|
||||
export const storeName = 'accountSettings';
|
||||
|
||||
export const usernameSelector = state => state.authentication.username;
|
||||
|
||||
export const userRolesSelector = state => state.authentication.roles || [];
|
||||
|
||||
export const accountSettingsSelector = state => ({ ...state[storeName] });
|
||||
|
||||
const duplicateTpaProviderSelector = state => state.errors.duplicateTpaProvider;
|
||||
|
||||
const configurationSelector = state => state.configuration;
|
||||
|
||||
const editableFieldNameSelector = (state, props) => props.name;
|
||||
|
||||
const valuesSelector = createSelector(
|
||||
@@ -172,8 +164,6 @@ export const accountSettingsPageSelector = createSelector(
|
||||
timeZonesSelector,
|
||||
countryTimeZonesSelector,
|
||||
activeAccountSelector,
|
||||
duplicateTpaProviderSelector,
|
||||
configurationSelector,
|
||||
(
|
||||
accountSettings,
|
||||
siteLanguageOptions,
|
||||
@@ -187,8 +177,6 @@ export const accountSettingsPageSelector = createSelector(
|
||||
timeZoneOptions,
|
||||
countryTimeZoneOptions,
|
||||
activeAccount,
|
||||
duplicateTpaProvider,
|
||||
configuration,
|
||||
) => ({
|
||||
siteLanguageOptions,
|
||||
siteLanguage,
|
||||
@@ -204,9 +192,6 @@ export const accountSettingsPageSelector = createSelector(
|
||||
profileDataManager,
|
||||
staticFields,
|
||||
hiddenFields,
|
||||
duplicateTpaProvider,
|
||||
tpaProviders: accountSettings.thirdPartyAuth.providers,
|
||||
supportUrl: configuration.SUPPORT_URL,
|
||||
logoutUrl: configuration.LOGOUT_URL,
|
||||
}),
|
||||
);
|
||||
@@ -1,22 +1,10 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import pick from 'lodash.pick';
|
||||
import omit from 'lodash.omit';
|
||||
import isEmpty from 'lodash.isempty';
|
||||
|
||||
import { applyConfiguration, handleRequestError, unpackFieldErrors } from '../common/serviceUtils';
|
||||
import { configureService as configureDeleteAccountApiService } from './delete-account';
|
||||
import { configureService as configureResetPasswordApiService } from './reset-password';
|
||||
import { configureService as configureSiteLanguageApiService } from './site-language';
|
||||
import { configureService as configureThirdPartyAuthApiService, getThirdPartyAuthProviders } from './third-party-auth';
|
||||
|
||||
let config = {
|
||||
BASE_URL: null,
|
||||
ACCOUNTS_API_BASE_URL: null,
|
||||
PREFERENCES_API_BASE_URL: null,
|
||||
ECOMMERCE_API_BASE_URL: null,
|
||||
LMS_BASE_URL: null,
|
||||
DELETE_ACCOUNT_URL: null,
|
||||
PASSWORD_RESET_URL: null,
|
||||
};
|
||||
import { handleRequestError, unpackFieldErrors } from './utils';
|
||||
import { getThirdPartyAuthProviders } from '../third-party-auth';
|
||||
|
||||
const SOCIAL_PLATFORMS = [
|
||||
{ id: 'twitter', key: 'social_link_twitter' },
|
||||
@@ -24,18 +12,6 @@ const SOCIAL_PLATFORMS = [
|
||||
{ id: 'linkedin', key: 'social_link_linkedin' },
|
||||
];
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
export function configureService(newConfig, newApiClient) {
|
||||
config = applyConfiguration(config, newConfig);
|
||||
apiClient = newApiClient;
|
||||
|
||||
configureDeleteAccountApiService(config, apiClient);
|
||||
configureResetPasswordApiService(config, apiClient);
|
||||
configureSiteLanguageApiService(config, apiClient);
|
||||
configureThirdPartyAuthApiService(config, apiClient);
|
||||
}
|
||||
|
||||
function unpackAccountResponseData(data) {
|
||||
const unpackedData = data;
|
||||
|
||||
@@ -90,7 +66,7 @@ function packAccountCommitData(commitData) {
|
||||
}
|
||||
|
||||
export async function getAccount(username) {
|
||||
const { data } = await apiClient.get(`${config.ACCOUNTS_API_BASE_URL}/${username}`);
|
||||
const { data } = await App.apiClient.get(`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}`);
|
||||
return unpackAccountResponseData(data);
|
||||
}
|
||||
|
||||
@@ -99,9 +75,9 @@ export async function patchAccount(username, commitValues) {
|
||||
headers: { 'Content-Type': 'application/merge-patch+json' },
|
||||
};
|
||||
|
||||
const { data } = await apiClient
|
||||
const { data } = await App.apiClient
|
||||
.patch(
|
||||
`${config.ACCOUNTS_API_BASE_URL}/${username}`,
|
||||
`${App.config.LMS_BASE_URL}/api/user/v1/accounts/${username}`,
|
||||
packAccountCommitData(commitValues),
|
||||
requestConfig,
|
||||
)
|
||||
@@ -122,23 +98,23 @@ export async function patchAccount(username, commitValues) {
|
||||
}
|
||||
|
||||
export async function getPreferences(username) {
|
||||
const { data } = await apiClient.get(`${config.PREFERENCES_API_BASE_URL}/${username}`);
|
||||
const { data } = await App.apiClient.get(`${App.config.LMS_BASE_URL}/api/user/v1/preferences/${username}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function patchPreferences(username, commitValues) {
|
||||
const requestConfig = { headers: { 'Content-Type': 'application/merge-patch+json' } };
|
||||
const requestUrl = `${config.PREFERENCES_API_BASE_URL}/${username}`;
|
||||
const requestUrl = `${App.config.LMS_BASE_URL}/api/user/v1/preferences/${username}`;
|
||||
|
||||
// Ignore the success response, the API does not currently return any data.
|
||||
await apiClient.patch(requestUrl, commitValues, requestConfig).catch(handleRequestError);
|
||||
await App.apiClient.patch(requestUrl, commitValues, requestConfig).catch(handleRequestError);
|
||||
|
||||
return commitValues;
|
||||
}
|
||||
|
||||
export async function getTimeZones(forCountry) {
|
||||
const { data } = await apiClient
|
||||
.get(`${config.LMS_BASE_URL}/user_api/v1/preferences/time_zones/`, {
|
||||
const { data } = await App.apiClient
|
||||
.get(`${App.config.LMS_BASE_URL}/user_api/v1/preferences/time_zones/`, {
|
||||
params: { country_code: forCountry },
|
||||
})
|
||||
.catch(handleRequestError);
|
||||
@@ -153,8 +129,8 @@ export async function getProfileDataManager(username, userRoles) {
|
||||
const userRoleNames = userRoles.map(role => role.split(':')[0]);
|
||||
|
||||
if (userRoleNames.includes('enterprise_learner')) {
|
||||
const url = `${config.LMS_BASE_URL}/enterprise/api/v1/enterprise-learner/?username=${username}`;
|
||||
const { data } = await apiClient.get(url).catch(handleRequestError);
|
||||
const url = `${App.config.LMS_BASE_URL}/enterprise/api/v1/enterprise-learner/?username=${username}`;
|
||||
const { data } = await App.apiClient.get(url).catch(handleRequestError);
|
||||
|
||||
if ('results' in data) {
|
||||
for (let i = 0; i < data.results.length; i += 1) {
|
||||
@@ -217,4 +193,3 @@ export async function patchSettings(username, commitValues) {
|
||||
const combinedResults = Object.assign({}, ...results);
|
||||
return combinedResults;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getModuleState should throw an exception on a bad path 1`] = `"Unexpected state key uhoh given to getModuleState. Is your state path set up correctly?"`;
|
||||
38
src/account-settings/data/utils/dataUtils.js
Normal file
38
src/account-settings/data/utils/dataUtils.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
export function modifyObjectKeys(object, modify) {
|
||||
// If the passed in object is not an object, return it.
|
||||
if (
|
||||
object === undefined ||
|
||||
object === null ||
|
||||
(typeof object !== 'object' && !Array.isArray(object))
|
||||
) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
return object.map(value => modifyObjectKeys(value, modify));
|
||||
}
|
||||
|
||||
// Otherwise, process all its keys.
|
||||
const result = {};
|
||||
Object.entries(object).forEach(([key, value]) => {
|
||||
result[modify(key)] = modifyObjectKeys(value, modify);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function camelCaseObject(object) {
|
||||
return modifyObjectKeys(object, camelCase);
|
||||
}
|
||||
|
||||
export function snakeCaseObject(object) {
|
||||
return modifyObjectKeys(object, snakeCase);
|
||||
}
|
||||
|
||||
export function convertKeyNames(object, nameMap) {
|
||||
const transformer = key => (nameMap[key] === undefined ? key : nameMap[key]);
|
||||
|
||||
return modifyObjectKeys(object, transformer);
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
import {
|
||||
AsyncActionType,
|
||||
modifyObjectKeys,
|
||||
camelCaseObject,
|
||||
snakeCaseObject,
|
||||
convertKeyNames,
|
||||
keepKeys,
|
||||
getModuleState,
|
||||
} from './utils';
|
||||
} from './dataUtils';
|
||||
|
||||
describe('modifyObjectKeys', () => {
|
||||
it('should use the provided modify function to change all keys in and object and its children', () => {
|
||||
@@ -91,77 +88,3 @@ describe('convertKeyNames', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('keepKeys', () => {
|
||||
it('should keep the specified keys only', () => {
|
||||
const result = keepKeys(
|
||||
{
|
||||
one: 123,
|
||||
two: { three: 'skip me' },
|
||||
four: 'five',
|
||||
six: null,
|
||||
8: 'sneaky',
|
||||
},
|
||||
[
|
||||
'one',
|
||||
'three',
|
||||
'six',
|
||||
'seven',
|
||||
'8', // yup, the 8 integer will be converted to a string.
|
||||
],
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
one: 123,
|
||||
six: null,
|
||||
8: 'sneaky',
|
||||
});
|
||||
});
|
||||
|
||||
describe('AsyncActionType', () => {
|
||||
it('should return well formatted action strings', () => {
|
||||
const actionType = new AsyncActionType('HOUSE_CATS', 'START_THE_RACE');
|
||||
|
||||
expect(actionType.BASE).toBe('HOUSE_CATS__START_THE_RACE');
|
||||
expect(actionType.BEGIN).toBe('HOUSE_CATS__START_THE_RACE__BEGIN');
|
||||
expect(actionType.SUCCESS).toBe('HOUSE_CATS__START_THE_RACE__SUCCESS');
|
||||
expect(actionType.FAILURE).toBe('HOUSE_CATS__START_THE_RACE__FAILURE');
|
||||
expect(actionType.RESET).toBe('HOUSE_CATS__START_THE_RACE__RESET');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getModuleState', () => {
|
||||
const state = {
|
||||
first: { red: { awesome: 'sauce' }, blue: { weak: 'sauce' } },
|
||||
second: { other: 'data' },
|
||||
};
|
||||
|
||||
it('should return everything if given an empty path', () => {
|
||||
expect(getModuleState(state, [])).toEqual(state);
|
||||
});
|
||||
|
||||
it('should resolve paths correctly', () => {
|
||||
expect(getModuleState(
|
||||
state,
|
||||
['first'],
|
||||
)).toEqual({ red: { awesome: 'sauce' }, blue: { weak: 'sauce' } });
|
||||
|
||||
expect(getModuleState(
|
||||
state,
|
||||
['first', 'red'],
|
||||
)).toEqual({ awesome: 'sauce' });
|
||||
|
||||
expect(getModuleState(state, ['second'])).toEqual({ other: 'data' });
|
||||
});
|
||||
|
||||
it('should throw an exception on a bad path', () => {
|
||||
expect(() => {
|
||||
getModuleState(state, ['uhoh']);
|
||||
}).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should return non-objects correctly', () => {
|
||||
expect(getModuleState(state, ['first', 'red', 'awesome'])).toEqual('sauce');
|
||||
});
|
||||
});
|
||||
});
|
||||
12
src/account-settings/data/utils/index.js
Normal file
12
src/account-settings/data/utils/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export {
|
||||
camelCaseObject,
|
||||
convertKeyNames,
|
||||
modifyObjectKeys,
|
||||
snakeCaseObject,
|
||||
} from './dataUtils';
|
||||
export {
|
||||
AsyncActionType,
|
||||
getModuleState,
|
||||
} from './reduxUtils';
|
||||
export { default as handleFailure } from './sagaUtils';
|
||||
export { unpackFieldErrors, handleRequestError } from './serviceUtils';
|
||||
@@ -1,50 +1,32 @@
|
||||
import camelCase from 'lodash.camelcase';
|
||||
import snakeCase from 'lodash.snakecase';
|
||||
|
||||
export function modifyObjectKeys(object, modify) {
|
||||
// If the passed in object is not an object, return it.
|
||||
if (
|
||||
object === undefined ||
|
||||
object === null ||
|
||||
(typeof object !== 'object' && !Array.isArray(object))
|
||||
) {
|
||||
return object;
|
||||
/**
|
||||
* Helper class to save time when writing out action types for asynchronous methods. Also helps
|
||||
* ensure that actions are namespaced.
|
||||
*/
|
||||
export class AsyncActionType {
|
||||
constructor(topic, name) {
|
||||
this.topic = topic;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
if (Array.isArray(object)) {
|
||||
return object.map(value => modifyObjectKeys(value, modify));
|
||||
get BASE() {
|
||||
return `${this.topic}__${this.name}`;
|
||||
}
|
||||
|
||||
// Otherwise, process all its keys.
|
||||
const result = {};
|
||||
Object.entries(object).forEach(([key, value]) => {
|
||||
result[modify(key)] = modifyObjectKeys(value, modify);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
get BEGIN() {
|
||||
return `${this.topic}__${this.name}__BEGIN`;
|
||||
}
|
||||
|
||||
export function camelCaseObject(object) {
|
||||
return modifyObjectKeys(object, camelCase);
|
||||
}
|
||||
get SUCCESS() {
|
||||
return `${this.topic}__${this.name}__SUCCESS`;
|
||||
}
|
||||
|
||||
export function snakeCaseObject(object) {
|
||||
return modifyObjectKeys(object, snakeCase);
|
||||
}
|
||||
get FAILURE() {
|
||||
return `${this.topic}__${this.name}__FAILURE`;
|
||||
}
|
||||
|
||||
export function convertKeyNames(object, nameMap) {
|
||||
const transformer = key => (nameMap[key] === undefined ? key : nameMap[key]);
|
||||
|
||||
return modifyObjectKeys(object, transformer);
|
||||
}
|
||||
|
||||
export function keepKeys(data, whitelist) {
|
||||
const result = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (whitelist.indexOf(key) > -1) {
|
||||
result[key] = data[key];
|
||||
}
|
||||
});
|
||||
return result;
|
||||
get RESET() {
|
||||
return `${this.topic}__${this.name}__RESET`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,36 +60,3 @@ export function getModuleState(state, originalPath) {
|
||||
}
|
||||
return getModuleState(state[key], path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to save time when writing out action types for asynchronous methods. Also helps
|
||||
* ensure that actions are namespaced.
|
||||
*
|
||||
* TODO: Put somewhere common to it can be used by other MFEs.
|
||||
*/
|
||||
export class AsyncActionType {
|
||||
constructor(topic, name) {
|
||||
this.topic = topic;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
get BASE() {
|
||||
return `${this.topic}__${this.name}`;
|
||||
}
|
||||
|
||||
get BEGIN() {
|
||||
return `${this.topic}__${this.name}__BEGIN`;
|
||||
}
|
||||
|
||||
get SUCCESS() {
|
||||
return `${this.topic}__${this.name}__SUCCESS`;
|
||||
}
|
||||
|
||||
get FAILURE() {
|
||||
return `${this.topic}__${this.name}__FAILURE`;
|
||||
}
|
||||
|
||||
get RESET() {
|
||||
return `${this.topic}__${this.name}__RESET`;
|
||||
}
|
||||
}
|
||||
51
src/account-settings/data/utils/reduxUtils.test.js
Normal file
51
src/account-settings/data/utils/reduxUtils.test.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
AsyncActionType,
|
||||
getModuleState,
|
||||
} from './reduxUtils';
|
||||
|
||||
describe('AsyncActionType', () => {
|
||||
it('should return well formatted action strings', () => {
|
||||
const actionType = new AsyncActionType('HOUSE_CATS', 'START_THE_RACE');
|
||||
|
||||
expect(actionType.BASE).toBe('HOUSE_CATS__START_THE_RACE');
|
||||
expect(actionType.BEGIN).toBe('HOUSE_CATS__START_THE_RACE__BEGIN');
|
||||
expect(actionType.SUCCESS).toBe('HOUSE_CATS__START_THE_RACE__SUCCESS');
|
||||
expect(actionType.FAILURE).toBe('HOUSE_CATS__START_THE_RACE__FAILURE');
|
||||
expect(actionType.RESET).toBe('HOUSE_CATS__START_THE_RACE__RESET');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getModuleState', () => {
|
||||
const state = {
|
||||
first: { red: { awesome: 'sauce' }, blue: { weak: 'sauce' } },
|
||||
second: { other: 'data' },
|
||||
};
|
||||
|
||||
it('should return everything if given an empty path', () => {
|
||||
expect(getModuleState(state, [])).toEqual(state);
|
||||
});
|
||||
|
||||
it('should resolve paths correctly', () => {
|
||||
expect(getModuleState(
|
||||
state,
|
||||
['first'],
|
||||
)).toEqual({ red: { awesome: 'sauce' }, blue: { weak: 'sauce' } });
|
||||
|
||||
expect(getModuleState(
|
||||
state,
|
||||
['first', 'red'],
|
||||
)).toEqual({ awesome: 'sauce' });
|
||||
|
||||
expect(getModuleState(state, ['second'])).toEqual({ other: 'data' });
|
||||
});
|
||||
|
||||
it('should throw an exception on a bad path', () => {
|
||||
expect(() => {
|
||||
getModuleState(state, ['uhoh']);
|
||||
}).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('should return non-objects correctly', () => {
|
||||
expect(getModuleState(state, ['first', 'red', 'awesome'])).toEqual('sauce');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { put } from 'redux-saga/effects';
|
||||
import { push } from 'connected-react-router';
|
||||
import { logAPIErrorResponse } from '@edx/frontend-logging';
|
||||
import { App } from '@edx/frontend-base';
|
||||
|
||||
export default function* handleFailure(error, failureAction = null, failureRedirectPath = null) {
|
||||
if (error.fieldErrors && failureAction !== null) {
|
||||
@@ -11,6 +11,6 @@ export default function* handleFailure(error, failureAction = null, failureRedir
|
||||
yield put(failureAction(error.message));
|
||||
}
|
||||
if (failureRedirectPath !== null) {
|
||||
yield put(push(failureRedirectPath));
|
||||
App.history.push(failureRedirectPath);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,3 @@
|
||||
import pick from 'lodash.pick';
|
||||
|
||||
export function applyConfiguration(expected, actual) {
|
||||
Object.keys(expected).forEach((key) => {
|
||||
if (actual[key] === undefined) {
|
||||
throw new Error(`Service configuration error: ${key} is required.`);
|
||||
}
|
||||
});
|
||||
return pick(actual, Object.keys(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns field errors of the form:
|
||||
*
|
||||
@@ -9,10 +9,10 @@ import { Hyperlink } from '@edx/paragon';
|
||||
import messages from './messages';
|
||||
|
||||
// Components
|
||||
import { Alert } from '../../common';
|
||||
import Alert from '../Alert';
|
||||
|
||||
const BeforeProceedingBanner = (props) => {
|
||||
const { instructionMessageId, intl, supportUrl } = props;
|
||||
const { instructionMessageId, intl, supportArticleUrl } = props;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
@@ -25,7 +25,7 @@ const BeforeProceedingBanner = (props) => {
|
||||
description="Error that appears if you are trying to delete your edX account, but something about your account needs attention first. The actionLink will be instructions, such as 'unlink your Facebook account'."
|
||||
values={{
|
||||
actionLink: (
|
||||
<Hyperlink destination={supportUrl}>
|
||||
<Hyperlink destination={supportArticleUrl}>
|
||||
{intl.formatMessage(messages[instructionMessageId])}
|
||||
</Hyperlink>
|
||||
),
|
||||
@@ -38,7 +38,7 @@ const BeforeProceedingBanner = (props) => {
|
||||
BeforeProceedingBanner.propTypes = {
|
||||
instructionMessageId: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
supportUrl: PropTypes.string.isRequired,
|
||||
supportArticleUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(BeforeProceedingBanner);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { faExclamationCircle, faExclamationTriangle } from '@fortawesome/free-so
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import messages from './messages';
|
||||
import { Alert } from '../../common';
|
||||
import Alert from '../Alert';
|
||||
import PrintingInstructions from './PrintingInstructions';
|
||||
|
||||
export class ConfirmationModal extends Component {
|
||||
|
||||
@@ -21,7 +21,6 @@ describe('ConfirmationModal', () => {
|
||||
status: null,
|
||||
errorType: null,
|
||||
password: 'fluffy bunnies',
|
||||
logoutUrl: 'http://localhost/logout',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { App } from '@edx/frontend-base';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-i18n';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
|
||||
@@ -46,7 +47,7 @@ export class DeleteAccount extends React.Component {
|
||||
};
|
||||
|
||||
handleFinalClose = () => {
|
||||
global.location = this.props.logoutUrl;
|
||||
global.location = App.config.LOGOUT_URL;
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -87,14 +88,14 @@ export class DeleteAccount extends React.Component {
|
||||
{isVerifiedAccount ? null : (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.activate"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/115000940568-How-do-I-activate-my-account-"
|
||||
/>
|
||||
)}
|
||||
|
||||
{hasLinkedTPA ? (
|
||||
<BeforeProceedingBanner
|
||||
instructionMessageId="account.settings.delete.account.please.unlink"
|
||||
supportUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
supportArticleUrl="https://support.edx.org/hc/en-us/articles/207206067"
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -123,7 +124,6 @@ DeleteAccount.propTypes = {
|
||||
errorType: PropTypes.oneOf(['empty-password', 'server']),
|
||||
hasLinkedTPA: PropTypes.bool,
|
||||
isVerifiedAccount: PropTypes.bool,
|
||||
logoutUrl: PropTypes.string.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ describe('DeleteAccount', () => {
|
||||
errorType: null,
|
||||
hasLinkedTPA: false,
|
||||
isVerifiedAccount: true,
|
||||
logoutUrl: 'http://localhost/logout',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { utils } from '../../../common';
|
||||
|
||||
const { AsyncActionType } = utils;
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const DELETE_ACCOUNT = new AsyncActionType('ACCOUNT_SETTINGS', 'DELETE_ACCOUNT');
|
||||
DELETE_ACCOUNT.CONFIRMATION = 'ACCOUNT_SETTINGS__DELETE_ACCOUNT__CONFIRMATION';
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import formurlencoded from 'form-urlencoded';
|
||||
import { applyConfiguration, handleRequestError } from '../../../common/serviceUtils';
|
||||
|
||||
let config = {
|
||||
DELETE_ACCOUNT_URL: null,
|
||||
};
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
export function configureService(newConfig, newApiClient) {
|
||||
config = applyConfiguration(config, newConfig);
|
||||
apiClient = newApiClient;
|
||||
}
|
||||
import { handleRequestError } from '../../data/utils';
|
||||
|
||||
/**
|
||||
* Request deletion of the user's account.
|
||||
*/
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function postDeleteAccount(password) {
|
||||
const { data } = await apiClient
|
||||
const { data } = await App.apiClient
|
||||
.post(
|
||||
config.DELETE_ACCOUNT_URL,
|
||||
`${App.config.LMS_BASE_URL}/api/user/v1/accounts/deactivate_logout/`,
|
||||
formurlencoded({ password }),
|
||||
{
|
||||
headers: {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { default } from './DeleteAccount';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { configureService } from './data/service';
|
||||
export { DELETE_ACCOUNT } from './data/actions';
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import ConnectedAccountSettingsPage from './AccountSettingsPage';
|
||||
import reducer from './reducers';
|
||||
import saga from './sagas';
|
||||
import { configureService } from './service';
|
||||
import { storeName } from './selectors';
|
||||
|
||||
export {
|
||||
configureService,
|
||||
ConnectedAccountSettingsPage,
|
||||
reducer,
|
||||
saga,
|
||||
storeName,
|
||||
};
|
||||
export { default } from './AccountSettingsPage';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { storeName } from './data/selectors';
|
||||
export { default as NotFoundPage } from './NotFoundPage';
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Hyperlink } from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Alert } from '../../common';
|
||||
import Alert from '../Alert';
|
||||
|
||||
const ConfirmationAlert = (props) => {
|
||||
const { email } = props;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { utils } from '../../../common';
|
||||
|
||||
const { AsyncActionType } = utils;
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const RESET_PASSWORD = new AsyncActionType('ACCOUNT_SETTINGS', 'RESET_PASSWORD');
|
||||
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import formurlencoded from 'form-urlencoded';
|
||||
import { applyConfiguration, handleRequestError } from '../../../common/serviceUtils';
|
||||
|
||||
let config = {
|
||||
PASSWORD_RESET_URL: null,
|
||||
};
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
export function configureService(newConfig, newApiClient) {
|
||||
config = applyConfiguration(config, newConfig);
|
||||
apiClient = newApiClient;
|
||||
}
|
||||
import { handleRequestError } from '../../data/utils';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export async function postResetPassword(email) {
|
||||
const { data } = await apiClient
|
||||
const { data } = await App.apiClient
|
||||
.post(
|
||||
config.PASSWORD_RESET_URL,
|
||||
`${App.config.LMS_BASE_URL}/password_reset/`,
|
||||
formurlencoded({ email }),
|
||||
{
|
||||
headers: {
|
||||
|
||||
@@ -2,4 +2,3 @@ export { default } from './ResetPassword';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { RESET_PASSWORD } from './data/actions';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { configureService } from './data/service';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncActionType } from '../../common/utils';
|
||||
import { AsyncActionType } from '../data/utils';
|
||||
|
||||
export const FETCH_SITE_LANGUAGES = new AsyncActionType('SITE_LANGUAGE', 'FETCH_SITE_LANGUAGES');
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import reducer from './reducers';
|
||||
import saga from './sagas';
|
||||
import { configureService, ApiService } from './service';
|
||||
import { siteLanguageOptionsSelector, siteLanguageListSelector } from './selectors';
|
||||
import { fetchSiteLanguages, FETCH_SITE_LANGUAGES } from './actions';
|
||||
|
||||
export { default as reducer } from './reducers';
|
||||
export { default as saga } from './sagas';
|
||||
export {
|
||||
ApiService,
|
||||
configureService,
|
||||
fetchSiteLanguages,
|
||||
FETCH_SITE_LANGUAGES,
|
||||
reducer,
|
||||
saga,
|
||||
siteLanguageListSelector,
|
||||
siteLanguageOptionsSelector,
|
||||
};
|
||||
getSiteLanguageList,
|
||||
patchPreferences,
|
||||
postSetLang,
|
||||
} from './service';
|
||||
export { siteLanguageOptionsSelector, siteLanguageListSelector } from './selectors';
|
||||
export { fetchSiteLanguages, FETCH_SITE_LANGUAGES } from './actions';
|
||||
|
||||
@@ -7,13 +7,13 @@ import {
|
||||
FETCH_SITE_LANGUAGES,
|
||||
} from './actions';
|
||||
|
||||
import { ApiService } from './service';
|
||||
import handleFailure from '../../common/sagaUtils';
|
||||
import { getSiteLanguageList } from './service';
|
||||
import { handleFailure } from '../data/utils';
|
||||
|
||||
function* handleFetchSiteLanguages() {
|
||||
try {
|
||||
yield put(fetchSiteLanguagesBegin());
|
||||
const siteLanguageList = yield call(ApiService.getSiteLanguageList);
|
||||
const siteLanguageList = yield call(getSiteLanguageList);
|
||||
yield put(fetchSiteLanguagesSuccess(siteLanguageList));
|
||||
} catch (e) {
|
||||
yield call(handleFailure, e, fetchSiteLanguagesFailure);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import { getModuleState } from '../../common/utils';
|
||||
import { getModuleState } from '../data/utils';
|
||||
|
||||
export const storePath = ['accountSettings', 'siteLanguage'];
|
||||
|
||||
|
||||
@@ -1,48 +1,29 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import siteLanguageList from './constants';
|
||||
import { snakeCaseObject, convertKeyNames } from '../../common/utils';
|
||||
import { applyConfiguration } from '../../common/serviceUtils';
|
||||
import { snakeCaseObject, convertKeyNames } from '../data/utils';
|
||||
|
||||
let config = {
|
||||
BASE_URL: null,
|
||||
PREFERENCES_API_BASE_URL: null,
|
||||
LMS_BASE_URL: null,
|
||||
};
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
export function configureService(newConfig, newApiClient) {
|
||||
config = applyConfiguration(config, newConfig);
|
||||
apiClient = newApiClient;
|
||||
}
|
||||
|
||||
async function getSiteLanguageList() {
|
||||
export async function getSiteLanguageList() {
|
||||
return siteLanguageList;
|
||||
}
|
||||
|
||||
async function patchPreferences(username, params) {
|
||||
export async function patchPreferences(username, params) {
|
||||
let processedParams = snakeCaseObject(params);
|
||||
processedParams = convertKeyNames(processedParams, {
|
||||
pref_lang: 'pref-lang',
|
||||
});
|
||||
|
||||
await apiClient.patch(`${config.PREFERENCES_API_BASE_URL}/${username}`, processedParams, {
|
||||
await App.apiClient.patch(`${App.config.LMS_BASE_URL}/api/user/v1/preferences/${username}`, processedParams, {
|
||||
headers: { 'Content-Type': 'application/merge-patch+json' },
|
||||
});
|
||||
|
||||
return params; // TODO: Once the server returns the updated preferences object, return that.
|
||||
}
|
||||
|
||||
async function postSetLang(code) {
|
||||
export async function postSetLang(code) {
|
||||
const formData = new FormData();
|
||||
formData.append('language', code);
|
||||
|
||||
await apiClient.post(`${config.LMS_BASE_URL}/i18n/setlang/`, formData, {
|
||||
await App.apiClient.post(`${App.config.LMS_BASE_URL}/i18n/setlang/`, formData, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
});
|
||||
}
|
||||
|
||||
export const ApiService = {
|
||||
getSiteLanguageList,
|
||||
patchPreferences,
|
||||
postSetLang,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { FormattedMessage } from '@edx/frontend-i18n';
|
||||
import { Hyperlink, StatefulButton } from '@edx/paragon';
|
||||
|
||||
import { Alert } from '../../common';
|
||||
import Alert from '../Alert';
|
||||
import { disconnectAuth } from './data/actions';
|
||||
|
||||
class ThirdPartyAuth extends Component {
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { utils } from '../../../common';
|
||||
|
||||
const { AsyncActionType } = utils;
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const DISCONNECT_AUTH = new AsyncActionType('ACCOUNT_SETTINGS', 'DISCONNECT_AUTH');
|
||||
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { applyConfiguration, handleRequestError } from '../../../common/serviceUtils';
|
||||
import { App } from '@edx/frontend-base';
|
||||
|
||||
let config = {
|
||||
LMS_BASE_URL: null,
|
||||
};
|
||||
|
||||
let apiClient = null;
|
||||
|
||||
export function configureService(newConfig, newApiClient) {
|
||||
config = applyConfiguration(config, newConfig);
|
||||
apiClient = newApiClient;
|
||||
}
|
||||
import { handleRequestError } from '../../data/utils';
|
||||
|
||||
export async function getThirdPartyAuthProviders() {
|
||||
const { data } = await apiClient
|
||||
.get(`${config.LMS_BASE_URL}/api/third_party_auth/v0/providers/user_status`)
|
||||
const { data } = await App.apiClient
|
||||
.get(`${App.config.LMS_BASE_URL}/api/third_party_auth/v0/providers/user_status`)
|
||||
.catch(handleRequestError);
|
||||
|
||||
return data.map(({ connect_url: connectUrl, disconnect_url: disconnectUrl, ...provider }) => ({
|
||||
...provider,
|
||||
connectUrl: `${config.LMS_BASE_URL}${connectUrl}`,
|
||||
disconnectUrl: `${config.LMS_BASE_URL}${disconnectUrl}`,
|
||||
connectUrl: `${App.config.LMS_BASE_URL}${connectUrl}`,
|
||||
disconnectUrl: `${App.config.LMS_BASE_URL}${disconnectUrl}`,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function postDisconnectAuth(url) {
|
||||
const { data } = await apiClient.post(url).catch(handleRequestError);
|
||||
const { data } = await App.apiClient.post(url).catch(handleRequestError);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { default } from './ThirdPartyAuth';
|
||||
export { default as reducer } from './data/reducers';
|
||||
export { default as saga } from './data/sagas';
|
||||
export { configureService, getThirdPartyAuthProviders } from './data/service';
|
||||
export { getThirdPartyAuthProviders, postDisconnectAuth } from './data/service';
|
||||
export { DISCONNECT_AUTH } from './data/actions';
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="1168px" height="540px" viewBox="0 0 1168 540" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 53.2 (72643) - https://sketchapp.com -->
|
||||
<title>logo</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="Path" fill="#209FDA" fill-rule="nonzero" points="1166.81993 85.5 1166.81993 2.84217094e-14 953.759925 2.84217094e-14 953.759925 85.5 1002.17993 85.5 915.859925 191.98 829.459925 85.5 878.099925 85.5 878.099925 2.84217094e-14 718.919925 2.84217094e-14 718.919925 95.72 856.479925 265.26 718.919925 434.96 718.919925 452.02 784.499925 452.02 784.499925 539.64 878.099925 539.64 878.099925 452.02 823.919925 452.02 915.919925 338.52 915.939925 338.52 1008.03993 452.02 953.759925 452.02 953.759925 539.64 1166.81993 539.64 1166.81993 452.02 1126.85993 452.02 975.319925 265.26 1121.01993 85.5"></polygon>
|
||||
<polygon id="Path" fill="#026BA4" fill-rule="nonzero" points="664.019925 7.10542736e-15 664.019925 85.5 710.619925 85.5 718.919925 95.72 718.919925 7.10542736e-15"></polygon>
|
||||
<polygon id="Path" fill="#026BA4" fill-rule="nonzero" points="718.919925 452.02 718.919925 434.96 705.079925 452.02 664.019925 452.02 664.019925 539.64 784.499925 539.64 784.499925 452.02"></polygon>
|
||||
<path d="M321.999925,411.86 L397.659925,411.86 C388.805702,433.829527 376.258024,454.122269 360.559925,471.86 C344.364089,454.216816 331.320914,433.921419 321.999925,411.86" id="Path" fill="#78212E" fill-rule="nonzero"></path>
|
||||
<path d="M360.559925,189.28 C338.58337,213.190393 322.501981,241.908137 313.599925,273.14 C317.134915,280.039338 320.007771,287.25831 322.179925,294.7 L397.059925,294.7 C399.306706,287.354671 402.25356,280.242036 405.859925,273.46 C397.464721,242.277678 381.959326,213.464341 360.559925,189.28 Z M322.179925,294.7 C328.784599,317.438017 328.978396,341.558795 322.739925,364.4 L396.399925,364.4 C389.855554,341.597488 390.06397,317.386469 396.999925,294.7 L322.179925,294.7 Z M322.179925,294.7 L308.679925,294.7 C304.690779,317.752715 304.575868,341.309464 308.339925,364.4 L322.739925,364.4 C328.978396,341.558795 328.784599,317.438017 322.179925,294.7 L322.179925,294.7 Z" id="Shape" fill="#78212E" fill-rule="nonzero"></path>
|
||||
<path d="M710.619925,85.5 L664.019925,85.5 L664.019925,0.02 L576.019925,0.02 L576.019925,85.5 L632.859925,85.5 L632.859925,159.2 C598.417874,134.487772 557.04992,121.286425 514.659925,121.48 C456.044663,121.405246 400.107354,146.01621 360.559925,189.28 C381.937732,213.470272 397.422343,242.283149 405.799925,273.46 C426.944121,233.500977 468.451514,208.51034 513.659925,208.52 C581.059925,208.52 632.879925,263.16 632.879925,330.52 L632.879925,331.2 C632.539925,398.28 580.879925,452.56 513.659925,452.56 C468.477451,452.593197 426.976426,427.652566 405.799925,387.74 L405.799925,387.74 C401.869213,380.340239 398.718926,372.551658 396.399925,364.5 L308.399925,364.5 C309.686934,372.450225 311.443338,380.317312 313.659925,388.06 C315.970162,396.190434 318.775397,404.171995 322.059925,411.96 L397.659925,411.96 C388.805702,433.929527 376.258024,454.222269 360.559925,471.96 C400.107354,515.22379 456.044663,539.834754 514.659925,539.76 C571.465111,540.091874 625.745998,516.316729 664.019925,474.34 L664.019925,452.04 L705.059925,452.04 L718.899925,434.96 L718.899925,95.74 L710.619925,85.5 Z M632.879925,501.9 L632.879925,539.74 L664.019925,539.74 L664.019925,474.18 C654.623775,484.469293 644.18821,493.758755 632.879925,501.9 L632.879925,501.9 Z M313.599925,273.14 C311.569597,280.231983 309.927163,287.429316 308.679925,294.7 L322.179925,294.7 C320.007771,287.25831 317.134915,280.039338 313.599925,273.14 L313.599925,273.14 Z" id="Shape" fill="#8A8C8F" fill-rule="nonzero"></path>
|
||||
<path d="M410.399925,294.7 C409.199925,287.5 407.659925,280.4 405.799925,273.46 C402.19356,280.242036 399.246706,287.354671 396.999925,294.7 C390.06397,317.386469 389.855554,341.597488 396.399925,364.4 L410.719925,364.4 C414.264276,341.293291 414.156293,317.77319 410.399925,294.7 L410.399925,294.7 Z M209.059925,121.48 C107.422724,121.487508 20.5081632,194.571683 3.05992537,294.7 L91.3999254,294.7 C107.135726,243.467257 154.465065,208.503753 208.059925,208.52 C252.638644,208.335148 293.496156,233.351373 313.599925,273.14 C322.501981,241.908137 338.58337,213.190393 360.559925,189.28 C322.206855,145.880863 266.976617,121.163964 209.059925,121.48 L209.059925,121.48 Z M297.479925,411.86 C275.077969,437.877726 242.392659,452.761934 208.059925,452.58 C153.691226,452.598435 105.87164,416.63791 90.7999254,364.4 L308.339925,364.4 C304.575868,341.309464 304.690779,317.752715 308.679925,294.7 L3.05992537,294.7 C-0.902504563,317.755068 -1.01739385,341.307372 2.71992537,364.4 L2.71992537,364.4 C19.3292424,465.441984 106.661918,539.594765 209.059925,539.6 C266.986094,539.900862 322.217868,515.161403 360.559925,471.74 C344.364089,454.096816 331.320914,433.801419 321.999925,411.74 L297.479925,411.86 Z" id="Shape" fill="#B72768" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`keepKeys getModuleState should throw an exception on a bad path 1`] = `"Unexpected state key uhoh given to getModuleState. Is your state path set up correctly?"`;
|
||||
@@ -1,11 +0,0 @@
|
||||
import { fetchUserAccount as _fetchUserAccount, UserAccountApiService } from '@edx/frontend-auth';
|
||||
|
||||
let userAccountApiService = null;
|
||||
|
||||
export function configureUserAccountApiService(configuration, apiClient) {
|
||||
userAccountApiService = new UserAccountApiService(apiClient, configuration.LMS_BASE_URL);
|
||||
}
|
||||
|
||||
export function fetchUserAccount(username) {
|
||||
return _fetchUserAccount(userAccountApiService, username);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { logAPIErrorResponse } from '@edx/frontend-logging';
|
||||
|
||||
import ErrorPage from './ErrorPage';
|
||||
|
||||
/*
|
||||
Error boundary component used to log caught errors and display the error page.
|
||||
*/
|
||||
export default class ErrorBoundary extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
logAPIErrorResponse(`${error} ${info}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return <ErrorPage />;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBoundary.propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
ErrorBoundary.defaultProps = {
|
||||
children: null,
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { FormattedMessage } from '@edx/frontend-i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
export default class ErrorPage extends Component {
|
||||
reload() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<p className="my-0 py-5 text-muted">
|
||||
<FormattedMessage
|
||||
id="unexpected.error.message.text"
|
||||
defaultMessage="An unexpected error occurred. Please click the button below to return to refresh the page."
|
||||
description="error message when an unexpected error occurs"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
onClick={this.reload}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="unexpected.error.button.text"
|
||||
defaultMessage="Try Again"
|
||||
description="text for button that tries to reload the app by refreshing the page"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import * as utils from './utils';
|
||||
import Alert from './components/Alert';
|
||||
import PageLoading from './components/PageLoading';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import SwitchContent from './components/SwitchContent';
|
||||
import { configureUserAccountApiService, fetchUserAccount } from './actions';
|
||||
|
||||
export {
|
||||
Alert,
|
||||
ErrorBoundary,
|
||||
PageLoading,
|
||||
SwitchContent,
|
||||
utils,
|
||||
configureUserAccountApiService,
|
||||
fetchUserAccount,
|
||||
};
|
||||
@@ -1,259 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect, Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { sendTrackEvent } from '@edx/frontend-analytics';
|
||||
import { IntlProvider, injectIntl, intlShape, getMessages } from '@edx/frontend-i18n';
|
||||
import SiteHeader from '@edx/frontend-component-site-header';
|
||||
import SiteFooter from '@edx/frontend-component-footer';
|
||||
|
||||
import {
|
||||
faFacebookSquare,
|
||||
faTwitterSquare,
|
||||
faLinkedin,
|
||||
faRedditSquare,
|
||||
} from '@fortawesome/free-brands-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { ErrorBoundary, fetchUserAccount } from '../common';
|
||||
import { ConnectedAccountSettingsPage } from '../account-settings';
|
||||
|
||||
import FooterLogo from '../assets/edx-footer.png';
|
||||
import HeaderLogo from '../assets/logo.svg';
|
||||
import NotFoundPage from './NotFoundPage';
|
||||
|
||||
import messages from './App.messages';
|
||||
|
||||
function PageContent({
|
||||
configuration,
|
||||
username,
|
||||
avatar,
|
||||
intl,
|
||||
}) {
|
||||
const mainMenu = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/dashboard`,
|
||||
content: intl.formatMessage(messages['siteheader.links.courses']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/dashboard/programs`,
|
||||
content: intl.formatMessage(messages['siteheader.links.programs']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.MARKETING_SITE_BASE_URL}/course`,
|
||||
content: intl.formatMessage(messages['siteheader.links.content.search']),
|
||||
onClick: () => {
|
||||
sendTrackEvent(
|
||||
'edx.bi.dashboard.find_courses_button.clicked',
|
||||
{ category: 'account', label: 'header' },
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const userMenu = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.dashboard']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/u/${username}`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.profile']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/account/settings`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.account.settings']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: configuration.ORDER_HISTORY_URL,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.order.history']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: configuration.LOGOUT_URL,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.logout']),
|
||||
},
|
||||
];
|
||||
const loggedOutItems = [
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/login`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.login']),
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
href: `${configuration.LMS_BASE_URL}/register`,
|
||||
content: intl.formatMessage(messages['siteheader.user.menu.register']),
|
||||
},
|
||||
];
|
||||
const socialLinks = [
|
||||
{
|
||||
title: 'Facebook',
|
||||
url: configuration.FACEBOOK_URL,
|
||||
icon: <FontAwesomeIcon icon={faFacebookSquare} className="social-icon" size="2x" />,
|
||||
screenReaderText: 'Like edX on Facebook',
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
url: configuration.TWITTER_URL,
|
||||
icon: <FontAwesomeIcon icon={faTwitterSquare} className="social-icon" size="2x" />,
|
||||
screenReaderText: 'Follow edX on Twitter',
|
||||
},
|
||||
{
|
||||
title: 'LinkedIn',
|
||||
url: configuration.LINKED_IN_URL,
|
||||
icon: <FontAwesomeIcon icon={faLinkedin} className="social-icon" size="2x" />,
|
||||
screenReaderText: 'Follow edX on LinkedIn',
|
||||
},
|
||||
{
|
||||
title: 'Reddit',
|
||||
url: configuration.REDDIT_URL,
|
||||
icon: <FontAwesomeIcon icon={faRedditSquare} className="social-icon" size="2x" />,
|
||||
screenReaderText: 'Subscribe to the edX subreddit',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div id="app">
|
||||
<SiteHeader
|
||||
logo={HeaderLogo}
|
||||
loggedIn
|
||||
username={username}
|
||||
avatar={avatar}
|
||||
logoAltText={configuration.SITE_NAME}
|
||||
logoDestination={`${configuration.LMS_BASE_URL}/dashboard`}
|
||||
mainMenu={mainMenu}
|
||||
userMenu={userMenu}
|
||||
loggedOutItems={loggedOutItems}
|
||||
/>
|
||||
<main>
|
||||
<Switch>
|
||||
<Route exact path="/" component={ConnectedAccountSettingsPage} />
|
||||
<Route path="/notfound" component={NotFoundPage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</main>
|
||||
<SiteFooter
|
||||
siteName={configuration.SITE_NAME}
|
||||
siteLogo={FooterLogo}
|
||||
marketingSiteBaseUrl={configuration.MARKETING_SITE_BASE_URL}
|
||||
supportUrl={configuration.SUPPORT_URL}
|
||||
contactUrl={configuration.CONTACT_URL}
|
||||
openSourceUrl={configuration.OPEN_SOURCE_URL}
|
||||
termsOfServiceUrl={configuration.TERMS_OF_SERVICE_URL}
|
||||
privacyPolicyUrl={configuration.PRIVACY_POLICY_URL}
|
||||
appleAppStoreUrl={configuration.APPLE_APP_STORE_URL}
|
||||
googlePlayUrl={configuration.GOOGLE_PLAY_URL}
|
||||
socialLinks={socialLinks}
|
||||
enterpriseMarketingLink={{
|
||||
url: configuration.ENTERPRISE_MARKETING_URL,
|
||||
queryParams: {
|
||||
utm_source: configuration.ENTERPRISE_MARKETING_UTM_SOURCE,
|
||||
utm_campaign: configuration.ENTERPRISE_MARKETING_UTM_CAMPAIGN,
|
||||
utm_medium: configuration.ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM,
|
||||
},
|
||||
}}
|
||||
handleAllTrackEvents={sendTrackEvent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const IntlPageContent = injectIntl(PageContent);
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
const { username } = this.props;
|
||||
this.props.fetchUserAccount(username);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<IntlProvider locale={this.props.locale} messages={getMessages()}>
|
||||
<Provider store={this.props.store}>
|
||||
<ConnectedRouter history={this.props.history}>
|
||||
<IntlPageContent
|
||||
configuration={this.props.configuration}
|
||||
username={this.props.username}
|
||||
avatar={this.props.avatar}
|
||||
/>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const configurationPropTypes = {
|
||||
SITE_NAME: PropTypes.string.isRequired,
|
||||
LMS_BASE_URL: PropTypes.string.isRequired,
|
||||
LOGOUT_URL: PropTypes.string.isRequired,
|
||||
MARKETING_SITE_BASE_URL: PropTypes.string.isRequired,
|
||||
SUPPORT_URL: PropTypes.string.isRequired,
|
||||
CONTACT_URL: PropTypes.string.isRequired,
|
||||
OPEN_SOURCE_URL: PropTypes.string.isRequired,
|
||||
TERMS_OF_SERVICE_URL: PropTypes.string.isRequired,
|
||||
PRIVACY_POLICY_URL: PropTypes.string.isRequired,
|
||||
FACEBOOK_URL: PropTypes.string.isRequired,
|
||||
TWITTER_URL: PropTypes.string.isRequired,
|
||||
YOU_TUBE_URL: PropTypes.string.isRequired,
|
||||
LINKED_IN_URL: PropTypes.string.isRequired,
|
||||
REDDIT_URL: PropTypes.string.isRequired,
|
||||
APPLE_APP_STORE_URL: PropTypes.string.isRequired,
|
||||
GOOGLE_PLAY_URL: PropTypes.string.isRequired,
|
||||
ORDER_HISTORY_URL: PropTypes.string.isRequired,
|
||||
ENTERPRISE_MARKETING_URL: PropTypes.string.isRequired,
|
||||
ENTERPRISE_MARKETING_UTM_SOURCE: PropTypes.string.isRequired,
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN: PropTypes.string.isRequired,
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
PageContent.propTypes = {
|
||||
username: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
configuration: PropTypes.shape(configurationPropTypes).isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
PageContent.defaultProps = {
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
App.propTypes = {
|
||||
fetchUserAccount: PropTypes.func.isRequired,
|
||||
username: PropTypes.string.isRequired,
|
||||
avatar: PropTypes.string,
|
||||
store: PropTypes.object.isRequired, // eslint-disable-line
|
||||
history: PropTypes.object.isRequired, // eslint-disable-line
|
||||
locale: PropTypes.string.isRequired,
|
||||
configuration: PropTypes.shape(configurationPropTypes).isRequired,
|
||||
};
|
||||
|
||||
App.defaultProps = {
|
||||
avatar: null,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
username: state.authentication.username,
|
||||
configuration: state.configuration,
|
||||
locale: state.i18n.locale,
|
||||
avatar: state.userAccount.profileImage.hasImage
|
||||
? state.userAccount.profileImage.imageUrlMedium
|
||||
: null,
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchUserAccount,
|
||||
},
|
||||
)(App);
|
||||
@@ -1,61 +0,0 @@
|
||||
import { defineMessages } from '@edx/frontend-i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
'siteheader.links.courses': {
|
||||
id: 'siteheader.links.courses',
|
||||
defaultMessage: 'Courses',
|
||||
description: 'Link to the learner course dashboard',
|
||||
},
|
||||
'siteheader.links.programs': {
|
||||
id: 'siteheader.links.programs',
|
||||
defaultMessage: 'Programs',
|
||||
description: 'Link to the learner program dashboard',
|
||||
},
|
||||
'siteheader.links.content.search': {
|
||||
id: 'siteheader.links.content.search',
|
||||
defaultMessage: 'Discover New',
|
||||
description: 'Link to the content search page',
|
||||
},
|
||||
'siteheader.user.menu.dashboard': {
|
||||
id: 'siteheader.user.menu.dashboard',
|
||||
defaultMessage: 'Dashboard',
|
||||
description: 'Link to the user dashboard',
|
||||
},
|
||||
'siteheader.user.menu.profile': {
|
||||
id: 'siteheader.user.menu.profile',
|
||||
defaultMessage: 'Profile',
|
||||
description: 'Link to the user profile',
|
||||
},
|
||||
'siteheader.user.menu.account.settings': {
|
||||
id: 'siteheader.user.menu.account.settings',
|
||||
defaultMessage: 'Account',
|
||||
description: 'Link to account settings',
|
||||
},
|
||||
'siteheader.user.menu.order.history': {
|
||||
id: 'siteheader.user.menu.order.history',
|
||||
defaultMessage: 'Order History',
|
||||
description: 'Link to order history',
|
||||
},
|
||||
'siteheader.user.menu.logout': {
|
||||
id: 'siteheader.user.menu.logout',
|
||||
defaultMessage: 'Logout',
|
||||
description: 'Logout link',
|
||||
},
|
||||
'siteheader.user.menu.login': {
|
||||
id: 'siteheader.user.menu.login',
|
||||
defaultMessage: 'Login',
|
||||
description: 'Login link',
|
||||
},
|
||||
'siteheader.user.menu.register': {
|
||||
id: 'siteheader.user.menu.register',
|
||||
defaultMessage: 'Sign Up',
|
||||
description: 'Link to registration',
|
||||
},
|
||||
'app.loading.message': {
|
||||
id: 'app.loading.message',
|
||||
defaultMessage: 'Loading...',
|
||||
description: 'Message shown when page content is loading.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
33
src/data/configureStore.js
Normal file
33
src/data/configureStore.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { App } from '@edx/frontend-base';
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||
import { createLogger } from 'redux-logger';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
|
||||
import createRootReducer from './reducers';
|
||||
import rootSaga from './sagas';
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
function composeMiddleware() {
|
||||
if (App.config.ENVIRONMENT === 'development') {
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
});
|
||||
return composeWithDevTools(applyMiddleware(thunkMiddleware, sagaMiddleware, loggerMiddleware));
|
||||
}
|
||||
|
||||
return compose(applyMiddleware(thunkMiddleware, sagaMiddleware));
|
||||
}
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
initialState,
|
||||
composeMiddleware(),
|
||||
);
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return store;
|
||||
}
|
||||
17
src/data/reducers.js
Executable file
17
src/data/reducers.js
Executable file
@@ -0,0 +1,17 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { userAccount } from '@edx/frontend-auth';
|
||||
import { reducer as i18nReducer } from '@edx/frontend-i18n';
|
||||
|
||||
import {
|
||||
reducer as accountSettingsReducer,
|
||||
storeName as accountSettingsStoreName,
|
||||
} from '../account-settings';
|
||||
|
||||
const createRootReducer = () =>
|
||||
combineReducers({
|
||||
i18n: i18nReducer,
|
||||
userAccount,
|
||||
[accountSettingsStoreName]: accountSettingsReducer,
|
||||
});
|
||||
|
||||
export default createRootReducer;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { all } from 'redux-saga/effects';
|
||||
import { saga as accountSettingsSaga } from './account-settings';
|
||||
import { saga as accountSettingsSaga } from '../account-settings';
|
||||
|
||||
export default function* rootSaga() {
|
||||
yield all([accountSettingsSaga()]);
|
||||
@@ -1,46 +0,0 @@
|
||||
export const configuration = {
|
||||
BASE_URL: process.env.BASE_URL,
|
||||
LMS_BASE_URL: process.env.LMS_BASE_URL,
|
||||
ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
|
||||
CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL,
|
||||
LOGIN_URL: process.env.LOGIN_URL,
|
||||
LOGOUT_URL: process.env.LOGOUT_URL,
|
||||
CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
|
||||
REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT,
|
||||
SEGMENT_KEY: process.env.SEGMENT_KEY,
|
||||
ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
|
||||
USER_INFO_COOKIE_NAME: process.env.USER_INFO_COOKIE_NAME,
|
||||
LANGUAGE_PREFERENCE_COOKIE_NAME: process.env.LANGUAGE_PREFERENCE_COOKIE_NAME,
|
||||
SITE_NAME: process.env.SITE_NAME,
|
||||
MARKETING_SITE_BASE_URL: process.env.MARKETING_SITE_BASE_URL,
|
||||
SUPPORT_URL: process.env.SUPPORT_URL,
|
||||
CONTACT_URL: process.env.CONTACT_URL,
|
||||
OPEN_SOURCE_URL: process.env.OPEN_SOURCE_URL,
|
||||
TERMS_OF_SERVICE_URL: process.env.TERMS_OF_SERVICE_URL,
|
||||
PRIVACY_POLICY_URL: process.env.PRIVACY_POLICY_URL,
|
||||
FACEBOOK_URL: process.env.FACEBOOK_URL,
|
||||
TWITTER_URL: process.env.TWITTER_URL,
|
||||
YOU_TUBE_URL: process.env.YOU_TUBE_URL,
|
||||
LINKED_IN_URL: process.env.LINKED_IN_URL,
|
||||
REDDIT_URL: process.env.REDDIT_URL,
|
||||
APPLE_APP_STORE_URL: process.env.APPLE_APP_STORE_URL,
|
||||
GOOGLE_PLAY_URL: process.env.GOOGLE_PLAY_URL,
|
||||
ACCOUNT_SETTINGS_URL: `${process.env.LMS_BASE_URL}/account/settings`,
|
||||
DATA_API_BASE_URL: process.env.DATA_API_BASE_URL,
|
||||
SECURE_COOKIES: process.env.NODE_ENV !== 'development',
|
||||
ENVIRONMENT: process.env.NODE_ENV,
|
||||
ACCOUNTS_API_BASE_URL: `${process.env.LMS_BASE_URL}/api/user/v1/accounts`,
|
||||
PREFERENCES_API_BASE_URL: `${process.env.LMS_BASE_URL}/api/user/v1/preferences`,
|
||||
CERTIFICATES_API_BASE_URL: `${process.env.LMS_BASE_URL}/api/certificates/v0/certificates`,
|
||||
VIEW_MY_RECORDS_URL: `${process.env.CREDENTIALS_BASE_URL}/records`,
|
||||
ECOMMERCE_API_BASE_URL: `${process.env.ECOMMERCE_BASE_URL}/api/v2`,
|
||||
ORDER_HISTORY_URL: process.env.ORDER_HISTORY_URL,
|
||||
DELETE_ACCOUNT_URL: `${process.env.LMS_BASE_URL}/api/user/v1/accounts/deactivate_logout/`,
|
||||
PASSWORD_RESET_URL: `${process.env.LMS_BASE_URL}/password_reset/`,
|
||||
ENTERPRISE_MARKETING_URL: process.env.ENTERPRISE_MARKETING_URL,
|
||||
ENTERPRISE_MARKETING_UTM_SOURCE: process.env.ENTERPRISE_MARKETING_UTM_SOURCE,
|
||||
ENTERPRISE_MARKETING_UTM_CAMPAIGN: process.env.ENTERPRISE_MARKETING_UTM_CAMPAIGN,
|
||||
ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM: process.env.ENTERPRISE_MARKETING_FOOTER_UTM_MEDIUM,
|
||||
};
|
||||
|
||||
export const features = {};
|
||||
125
src/index.jsx
125
src/index.jsx
@@ -1,99 +1,52 @@
|
||||
import 'babel-polyfill';
|
||||
import 'url-polyfill';
|
||||
import 'formdata-polyfill';
|
||||
import { App, AppProvider, APP_ERROR, APP_READY, ErrorPage } from '@edx/frontend-base';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {
|
||||
configureAnalytics,
|
||||
identifyAnonymousUser,
|
||||
identifyAuthenticatedUser,
|
||||
initializeSegment,
|
||||
sendPageEvent,
|
||||
sendTrackingLogEvent,
|
||||
} from '@edx/frontend-analytics';
|
||||
import { configureLoggingService, NewRelicLoggingService } from '@edx/frontend-logging';
|
||||
import { getAuthenticatedAPIClient } from '@edx/frontend-auth';
|
||||
import { configure as configureI18n } from '@edx/frontend-i18n';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { configuration } from './environment';
|
||||
import configureStore from './store';
|
||||
import { configureUserAccountApiService } from './common';
|
||||
import { configureService as configureAccountSettingsApiService } from './account-settings';
|
||||
import messages from './i18n';
|
||||
import App from './components/App';
|
||||
import Header, { messages as headerMessages } from '@edx/frontend-component-header';
|
||||
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';
|
||||
|
||||
import configureStore from './data/configureStore';
|
||||
import AccountSettingsPage, { NotFoundPage } from './account-settings';
|
||||
import appMessages from './i18n';
|
||||
|
||||
import './index.scss';
|
||||
import './assets/favicon.ico';
|
||||
|
||||
const apiClient = getAuthenticatedAPIClient({
|
||||
appBaseUrl: configuration.BASE_URL,
|
||||
authBaseUrl: configuration.LMS_BASE_URL,
|
||||
loginUrl: configuration.LOGIN_URL,
|
||||
logoutUrl: configuration.LOGOUT_URL,
|
||||
csrfTokenApiPath: configuration.CSRF_TOKEN_API_PATH,
|
||||
refreshAccessTokenEndpoint: configuration.REFRESH_ACCESS_TOKEN_ENDPOINT,
|
||||
accessTokenCookieName: configuration.ACCESS_TOKEN_COOKIE_NAME,
|
||||
userInfoCookieName: configuration.USER_INFO_COOKIE_NAME,
|
||||
loggingService: NewRelicLoggingService,
|
||||
App.subscribe(APP_READY, () => {
|
||||
ReactDOM.render(
|
||||
<AppProvider store={configureStore()}>
|
||||
<Header />
|
||||
<main>
|
||||
<Switch>
|
||||
<Route exact path="/" component={AccountSettingsPage} />
|
||||
<Route path="/notfound" component={NotFoundPage} />
|
||||
<Route path="*" component={NotFoundPage} />
|
||||
</Switch>
|
||||
</main>
|
||||
<Footer />
|
||||
</AppProvider>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* We need to merge the application configuration with the authentication state
|
||||
* so that we can hand it all to the redux store's initializer.
|
||||
*/
|
||||
function createInitialState() {
|
||||
const errors = {};
|
||||
const url = new URL(window.location.href);
|
||||
App.subscribe(APP_ERROR, (error) => {
|
||||
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
|
||||
});
|
||||
|
||||
// Extract duplicate third-party auth provider message from query string
|
||||
errors.duplicateTpaProvider = url.searchParams.get('duplicate_provider');
|
||||
if (errors.duplicateTpaProvider) {
|
||||
// Remove the duplicate_provider query param to avoid bookmarking.
|
||||
window.history.replaceState(null, '', `${url.protocol}//${url.host}${url.pathname}`);
|
||||
}
|
||||
|
||||
return Object.assign({}, { configuration }, apiClient.getAuthenticationState(), { errors });
|
||||
}
|
||||
|
||||
function configure() {
|
||||
configureI18n(configuration, messages);
|
||||
|
||||
const { store, history } = configureStore(createInitialState(), configuration.ENVIRONMENT);
|
||||
|
||||
configureLoggingService(NewRelicLoggingService);
|
||||
configureAccountSettingsApiService(configuration, apiClient);
|
||||
configureUserAccountApiService(configuration, apiClient);
|
||||
initializeSegment(configuration.SEGMENT_KEY);
|
||||
configureAnalytics({
|
||||
loggingService: NewRelicLoggingService,
|
||||
authApiClient: apiClient,
|
||||
analyticsApiBaseUrl: configuration.LMS_BASE_URL,
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
history,
|
||||
};
|
||||
}
|
||||
|
||||
apiClient.ensurePublicOrAuthenticationAndCookies(
|
||||
window.location.pathname,
|
||||
(accessToken) => {
|
||||
const { store, history } = configure();
|
||||
|
||||
ReactDOM.render(<App store={store} history={history} />, document.getElementById('root'));
|
||||
|
||||
if (accessToken) {
|
||||
identifyAuthenticatedUser(accessToken.userId);
|
||||
} else {
|
||||
identifyAnonymousUser();
|
||||
}
|
||||
sendPageEvent();
|
||||
|
||||
sendTrackingLogEvent('edx.user.settings.viewed', {
|
||||
page: 'account',
|
||||
visibility: null,
|
||||
user_id: accessToken ? accessToken.userId : null,
|
||||
});
|
||||
App.initialize({
|
||||
messages: [
|
||||
appMessages,
|
||||
headerMessages,
|
||||
footerMessages,
|
||||
],
|
||||
overrideHandlers: {
|
||||
loadConfig: () => {
|
||||
App.mergeConfig({
|
||||
SUPPORT_URL: process.env.SUPPORT_URL,
|
||||
}, 'App loadConfig override handler');
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
$fa-font-path: "~font-awesome/fonts";
|
||||
@import "~font-awesome/scss/font-awesome";
|
||||
|
||||
@import "~@edx/frontend-component-site-header/src/index";
|
||||
@import "~@edx/frontend-component-footer/src/lib/scss/site-footer";
|
||||
@import "~@edx/frontend-component-header/dist/index";
|
||||
@import "~@edx/frontend-component-footer/dist/footer";
|
||||
|
||||
@import "./account-settings/style";
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { userAccount } from '@edx/frontend-auth';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
|
||||
import { reducer as i18nReducer } from '@edx/frontend-i18n'; // eslint-disable-line
|
||||
|
||||
import {
|
||||
reducer as accountSettingsReducer,
|
||||
storeName as accountSettingsStoreName,
|
||||
} from './account-settings';
|
||||
|
||||
const identityReducer = (state) => {
|
||||
const newState = { ...state };
|
||||
return newState;
|
||||
};
|
||||
|
||||
const createRootReducer = history =>
|
||||
combineReducers({
|
||||
// The authentication state is added as initialState when
|
||||
// creating the store in data/store.js.
|
||||
authentication: identityReducer,
|
||||
configuration: identityReducer,
|
||||
errors: identityReducer,
|
||||
i18n: i18nReducer,
|
||||
userAccount,
|
||||
[accountSettingsStoreName]: accountSettingsReducer,
|
||||
router: connectRouter(history),
|
||||
});
|
||||
|
||||
export default createRootReducer;
|
||||
@@ -4,7 +4,3 @@ import Enzyme from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-16';
|
||||
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
// These configuration values are usually set in webpack's EnvironmentPlugin however
|
||||
// Jest does not use webpack so we need to set these so for testing
|
||||
process.env.LMS_BASE_URL = 'http://localhost:18000';
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { applyMiddleware, createStore } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { routerMiddleware } from 'connected-react-router';
|
||||
import { composeWithDevTools } from 'redux-devtools-extension/logOnlyInProduction';
|
||||
import { createLogger } from 'redux-logger';
|
||||
|
||||
import createRootReducer from '../reducers';
|
||||
import rootSaga from '../sagas';
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const loggerMiddleware = createLogger({
|
||||
collapsed: true,
|
||||
});
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(history),
|
||||
initialState,
|
||||
composeWithDevTools(applyMiddleware(thunkMiddleware, sagaMiddleware, routerMiddleware(history), loggerMiddleware)), // eslint-disable-line
|
||||
);
|
||||
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return { store, history };
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { applyMiddleware, createStore, compose } from 'redux';
|
||||
import createSagaMiddleware from 'redux-saga';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { routerMiddleware } from 'connected-react-router';
|
||||
|
||||
import createRootReducer from '../reducers';
|
||||
import rootSaga from '../sagas';
|
||||
|
||||
export default function configureStore(initialState = {}) {
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const sagaMiddleware = createSagaMiddleware();
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(history),
|
||||
initialState,
|
||||
compose(applyMiddleware(thunkMiddleware, sagaMiddleware, routerMiddleware(history))),
|
||||
);
|
||||
|
||||
sagaMiddleware.run(rootSaga);
|
||||
|
||||
return { store, history };
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import configureStoreProd from './configureStore.prod';
|
||||
import configureStoreDev from './configureStore.dev';
|
||||
|
||||
export default function configureStore(state, env) {
|
||||
if (env === 'production') {
|
||||
return configureStoreProd(state);
|
||||
}
|
||||
return configureStoreDev(state);
|
||||
}
|
||||
Reference in New Issue
Block a user