diff --git a/.env b/.env index 8fb0195..34dbdf6 100644 --- a/.env +++ b/.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 diff --git a/.env.development b/.env.development index 2e5bd56..5bbfc73 100644 --- a/.env.development +++ b/.env.development @@ -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' diff --git a/.env.test b/.env.test index e18a869..b76b0c3 100644 --- a/.env.test +++ b/.env.test @@ -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' diff --git a/package-lock.json b/package-lock.json index 96f836a..76ea3a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 9332bc5..75748c3 100755 --- a/package.json +++ b/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", diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx index b396c10..cdbffe2 100644 --- a/src/account-settings/AccountSettingsPage.jsx +++ b/src/account-settings/AccountSettingsPage.jsx @@ -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: {this.props.duplicateTpaProvider}, + provider: {this.state.duplicateTpaProvider}, }} /> @@ -132,7 +151,7 @@ class AccountSettingsPage extends React.Component { values={{ managerTitle: {this.props.profileDataManager}, support: ( - + @@ -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, diff --git a/src/common/components/Alert.jsx b/src/account-settings/Alert.jsx similarity index 100% rename from src/common/components/Alert.jsx rename to src/account-settings/Alert.jsx diff --git a/src/account-settings/BetaLanguageBanner.jsx b/src/account-settings/BetaLanguageBanner.jsx index 425dd37..a9da040 100644 --- a/src/account-settings/BetaLanguageBanner.jsx +++ b/src/account-settings/BetaLanguageBanner.jsx @@ -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) { diff --git a/src/account-settings/EditableField.jsx b/src/account-settings/EditableField.jsx index 3ce088b..7bea745 100644 --- a/src/account-settings/EditableField.jsx +++ b/src/account-settings/EditableField.jsx @@ -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) { diff --git a/src/account-settings/EmailField.jsx b/src/account-settings/EmailField.jsx index 865e57f..2e21846 100644 --- a/src/account-settings/EmailField.jsx +++ b/src/account-settings/EmailField.jsx @@ -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) { diff --git a/src/components/NotFoundPage.jsx b/src/account-settings/NotFoundPage.jsx similarity index 100% rename from src/components/NotFoundPage.jsx rename to src/account-settings/NotFoundPage.jsx diff --git a/src/common/components/PageLoading.jsx b/src/account-settings/PageLoading.jsx similarity index 100% rename from src/common/components/PageLoading.jsx rename to src/account-settings/PageLoading.jsx diff --git a/src/common/components/SwitchContent.jsx b/src/account-settings/SwitchContent.jsx similarity index 100% rename from src/common/components/SwitchContent.jsx rename to src/account-settings/SwitchContent.jsx diff --git a/src/account-settings/actions.js b/src/account-settings/data/actions.js similarity index 97% rename from src/account-settings/actions.js rename to src/account-settings/data/actions.js index a04b5d0..f66035b 100644 --- a/src/account-settings/actions.js +++ b/src/account-settings/data/actions.js @@ -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'); diff --git a/src/account-settings/constants.js b/src/account-settings/data/constants.js similarity index 100% rename from src/account-settings/constants.js rename to src/account-settings/data/constants.js diff --git a/src/account-settings/reducers.js b/src/account-settings/data/reducers.js similarity index 98% rename from src/account-settings/reducers.js rename to src/account-settings/data/reducers.js index 99d20f6..c41bfe4 100644 --- a/src/account-settings/reducers.js +++ b/src/account-settings/data/reducers.js @@ -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, diff --git a/src/account-settings/sagas.js b/src/account-settings/data/sagas.js similarity index 70% rename from src/account-settings/sagas.js rename to src/account-settings/data/sagas.js index 08d5c3f..9b27de3 100644 --- a/src/account-settings/sagas.js +++ b/src/account-settings/data/sagas.js @@ -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)); } diff --git a/src/account-settings/selectors.js b/src/account-settings/data/selectors.js similarity index 91% rename from src/account-settings/selectors.js rename to src/account-settings/data/selectors.js index f2f1756..020653c 100644 --- a/src/account-settings/selectors.js +++ b/src/account-settings/data/selectors.js @@ -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, }), ); diff --git a/src/account-settings/service.js b/src/account-settings/data/service.js similarity index 75% rename from src/account-settings/service.js rename to src/account-settings/data/service.js index d7e6a75..8770b1d 100644 --- a/src/account-settings/service.js +++ b/src/account-settings/data/service.js @@ -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; } - diff --git a/src/account-settings/data/utils/__snapshots__/reduxUtils.test.js.snap b/src/account-settings/data/utils/__snapshots__/reduxUtils.test.js.snap new file mode 100644 index 0000000..5571ec8 --- /dev/null +++ b/src/account-settings/data/utils/__snapshots__/reduxUtils.test.js.snap @@ -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?"`; diff --git a/src/account-settings/data/utils/dataUtils.js b/src/account-settings/data/utils/dataUtils.js new file mode 100644 index 0000000..cb1255e --- /dev/null +++ b/src/account-settings/data/utils/dataUtils.js @@ -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); +} diff --git a/src/common/utils.test.js b/src/account-settings/data/utils/dataUtils.test.js similarity index 50% rename from src/common/utils.test.js rename to src/account-settings/data/utils/dataUtils.test.js index 40c4b44..dee7558 100644 --- a/src/common/utils.test.js +++ b/src/account-settings/data/utils/dataUtils.test.js @@ -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'); - }); - }); -}); diff --git a/src/account-settings/data/utils/index.js b/src/account-settings/data/utils/index.js new file mode 100644 index 0000000..e8c75a2 --- /dev/null +++ b/src/account-settings/data/utils/index.js @@ -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'; diff --git a/src/common/utils.js b/src/account-settings/data/utils/reduxUtils.js similarity index 52% rename from src/common/utils.js rename to src/account-settings/data/utils/reduxUtils.js index 9147dd3..0a75d19 100644 --- a/src/common/utils.js +++ b/src/account-settings/data/utils/reduxUtils.js @@ -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`; - } -} diff --git a/src/account-settings/data/utils/reduxUtils.test.js b/src/account-settings/data/utils/reduxUtils.test.js new file mode 100644 index 0000000..586a8ba --- /dev/null +++ b/src/account-settings/data/utils/reduxUtils.test.js @@ -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'); + }); +}); diff --git a/src/common/sagaUtils.js b/src/account-settings/data/utils/sagaUtils.js similarity index 84% rename from src/common/sagaUtils.js rename to src/account-settings/data/utils/sagaUtils.js index c28a24b..721891b 100644 --- a/src/common/sagaUtils.js +++ b/src/account-settings/data/utils/sagaUtils.js @@ -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); } } diff --git a/src/common/serviceUtils.js b/src/account-settings/data/utils/serviceUtils.js similarity index 82% rename from src/common/serviceUtils.js rename to src/account-settings/data/utils/serviceUtils.js index 519dbe3..ea22c04 100644 --- a/src/common/serviceUtils.js +++ b/src/account-settings/data/utils/serviceUtils.js @@ -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: * diff --git a/src/account-settings/delete-account/BeforeProceedingBanner.jsx b/src/account-settings/delete-account/BeforeProceedingBanner.jsx index 0c6afca..83246db 100644 --- a/src/account-settings/delete-account/BeforeProceedingBanner.jsx +++ b/src/account-settings/delete-account/BeforeProceedingBanner.jsx @@ -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 ( { 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: ( - + {intl.formatMessage(messages[instructionMessageId])} ), @@ -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); diff --git a/src/account-settings/delete-account/ConfirmationModal.jsx b/src/account-settings/delete-account/ConfirmationModal.jsx index 00a178f..c7ce734 100644 --- a/src/account-settings/delete-account/ConfirmationModal.jsx +++ b/src/account-settings/delete-account/ConfirmationModal.jsx @@ -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 { diff --git a/src/account-settings/delete-account/ConfirmationModal.test.jsx b/src/account-settings/delete-account/ConfirmationModal.test.jsx index dd18294..d85c7cb 100644 --- a/src/account-settings/delete-account/ConfirmationModal.test.jsx +++ b/src/account-settings/delete-account/ConfirmationModal.test.jsx @@ -21,7 +21,6 @@ describe('ConfirmationModal', () => { status: null, errorType: null, password: 'fluffy bunnies', - logoutUrl: 'http://localhost/logout', }; }); diff --git a/src/account-settings/delete-account/DeleteAccount.jsx b/src/account-settings/delete-account/DeleteAccount.jsx index dd43ed0..b75da95 100644 --- a/src/account-settings/delete-account/DeleteAccount.jsx +++ b/src/account-settings/delete-account/DeleteAccount.jsx @@ -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 : ( )} {hasLinkedTPA ? ( ) : 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, }; diff --git a/src/account-settings/delete-account/DeleteAccount.test.jsx b/src/account-settings/delete-account/DeleteAccount.test.jsx index 7006e69..3779541 100644 --- a/src/account-settings/delete-account/DeleteAccount.test.jsx +++ b/src/account-settings/delete-account/DeleteAccount.test.jsx @@ -24,7 +24,6 @@ describe('DeleteAccount', () => { errorType: null, hasLinkedTPA: false, isVerifiedAccount: true, - logoutUrl: 'http://localhost/logout', }; }); diff --git a/src/account-settings/delete-account/data/actions.js b/src/account-settings/delete-account/data/actions.js index 5fbea5e..3653164 100644 --- a/src/account-settings/delete-account/data/actions.js +++ b/src/account-settings/delete-account/data/actions.js @@ -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'; diff --git a/src/account-settings/delete-account/data/service.js b/src/account-settings/delete-account/data/service.js index 7d786ed..c05144f 100644 --- a/src/account-settings/delete-account/data/service.js +++ b/src/account-settings/delete-account/data/service.js @@ -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: { diff --git a/src/account-settings/delete-account/index.js b/src/account-settings/delete-account/index.js index 12e2e06..ae71f2c 100644 --- a/src/account-settings/delete-account/index.js +++ b/src/account-settings/delete-account/index.js @@ -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'; diff --git a/src/account-settings/index.js b/src/account-settings/index.js index 72421b2..900fb2c 100644 --- a/src/account-settings/index.js +++ b/src/account-settings/index.js @@ -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'; diff --git a/src/account-settings/reset-password/ConfirmationAlert.jsx b/src/account-settings/reset-password/ConfirmationAlert.jsx index f666cea..77a5e5d 100644 --- a/src/account-settings/reset-password/ConfirmationAlert.jsx +++ b/src/account-settings/reset-password/ConfirmationAlert.jsx @@ -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; diff --git a/src/account-settings/reset-password/data/actions.js b/src/account-settings/reset-password/data/actions.js index 599825a..c184792 100644 --- a/src/account-settings/reset-password/data/actions.js +++ b/src/account-settings/reset-password/data/actions.js @@ -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'); diff --git a/src/account-settings/reset-password/data/service.js b/src/account-settings/reset-password/data/service.js index 1a383eb..cae2e17 100644 --- a/src/account-settings/reset-password/data/service.js +++ b/src/account-settings/reset-password/data/service.js @@ -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: { diff --git a/src/account-settings/reset-password/index.js b/src/account-settings/reset-password/index.js index bba2e23..db1df77 100644 --- a/src/account-settings/reset-password/index.js +++ b/src/account-settings/reset-password/index.js @@ -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'; diff --git a/src/account-settings/site-language/actions.js b/src/account-settings/site-language/actions.js index b89da45..d10e64b 100644 --- a/src/account-settings/site-language/actions.js +++ b/src/account-settings/site-language/actions.js @@ -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'); diff --git a/src/account-settings/site-language/index.js b/src/account-settings/site-language/index.js index 4053449..57edb7b 100644 --- a/src/account-settings/site-language/index.js +++ b/src/account-settings/site-language/index.js @@ -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'; diff --git a/src/account-settings/site-language/sagas.js b/src/account-settings/site-language/sagas.js index d2d5f5c..461e384 100644 --- a/src/account-settings/site-language/sagas.js +++ b/src/account-settings/site-language/sagas.js @@ -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); diff --git a/src/account-settings/site-language/selectors.js b/src/account-settings/site-language/selectors.js index 9c000da..3194a28 100644 --- a/src/account-settings/site-language/selectors.js +++ b/src/account-settings/site-language/selectors.js @@ -1,5 +1,5 @@ import { createSelector } from 'reselect'; -import { getModuleState } from '../../common/utils'; +import { getModuleState } from '../data/utils'; export const storePath = ['accountSettings', 'siteLanguage']; diff --git a/src/account-settings/site-language/service.js b/src/account-settings/site-language/service.js index 282007a..b8a8117 100644 --- a/src/account-settings/site-language/service.js +++ b/src/account-settings/site-language/service.js @@ -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, -}; diff --git a/src/account-settings/third-party-auth/ThirdPartyAuth.jsx b/src/account-settings/third-party-auth/ThirdPartyAuth.jsx index 33984d5..326f775 100644 --- a/src/account-settings/third-party-auth/ThirdPartyAuth.jsx +++ b/src/account-settings/third-party-auth/ThirdPartyAuth.jsx @@ -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 { diff --git a/src/account-settings/third-party-auth/data/actions.js b/src/account-settings/third-party-auth/data/actions.js index ef5dade..f0e6569 100644 --- a/src/account-settings/third-party-auth/data/actions.js +++ b/src/account-settings/third-party-auth/data/actions.js @@ -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'); diff --git a/src/account-settings/third-party-auth/data/service.js b/src/account-settings/third-party-auth/data/service.js index ebbfa5b..5721f29 100644 --- a/src/account-settings/third-party-auth/data/service.js +++ b/src/account-settings/third-party-auth/data/service.js @@ -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; } diff --git a/src/account-settings/third-party-auth/index.js b/src/account-settings/third-party-auth/index.js index 84e23c8..08fc1da 100644 --- a/src/account-settings/third-party-auth/index.js +++ b/src/account-settings/third-party-auth/index.js @@ -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'; diff --git a/src/assets/dot-pattern-light.png b/src/assets/dot-pattern-light.png deleted file mode 100644 index c84a3c5..0000000 Binary files a/src/assets/dot-pattern-light.png and /dev/null differ diff --git a/src/assets/edx-footer.png b/src/assets/edx-footer.png deleted file mode 100644 index 954e6e9..0000000 Binary files a/src/assets/edx-footer.png and /dev/null differ diff --git a/src/assets/edx-sm.png b/src/assets/edx-sm.png deleted file mode 100644 index d185835..0000000 Binary files a/src/assets/edx-sm.png and /dev/null differ diff --git a/src/assets/logo.svg b/src/assets/logo.svg deleted file mode 100644 index 1fbac45..0000000 --- a/src/assets/logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - logo - Created with Sketch. - - \ No newline at end of file diff --git a/src/common/__snapshots__/utils.test.js.snap b/src/common/__snapshots__/utils.test.js.snap deleted file mode 100644 index b8c2a30..0000000 --- a/src/common/__snapshots__/utils.test.js.snap +++ /dev/null @@ -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?"`; diff --git a/src/common/actions.js b/src/common/actions.js deleted file mode 100644 index 2a17c48..0000000 --- a/src/common/actions.js +++ /dev/null @@ -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); -} diff --git a/src/common/components/ErrorBoundary.jsx b/src/common/components/ErrorBoundary.jsx deleted file mode 100644 index d0d09c8..0000000 --- a/src/common/components/ErrorBoundary.jsx +++ /dev/null @@ -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 ; - } - - return this.props.children; - } -} - -ErrorBoundary.propTypes = { - children: PropTypes.node, -}; - -ErrorBoundary.defaultProps = { - children: null, -}; diff --git a/src/common/components/ErrorPage.jsx b/src/common/components/ErrorPage.jsx deleted file mode 100644 index 1d2b495..0000000 --- a/src/common/components/ErrorPage.jsx +++ /dev/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 ( -
-
-
-

- -

-
-
-
-
-
-
-
- ); - } -} diff --git a/src/common/index.js b/src/common/index.js deleted file mode 100644 index 7ff1a09..0000000 --- a/src/common/index.js +++ /dev/null @@ -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, -}; diff --git a/src/components/App.jsx b/src/components/App.jsx deleted file mode 100644 index 96739d6..0000000 --- a/src/components/App.jsx +++ /dev/null @@ -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: , - screenReaderText: 'Like edX on Facebook', - }, - { - title: 'Twitter', - url: configuration.TWITTER_URL, - icon: , - screenReaderText: 'Follow edX on Twitter', - }, - { - title: 'LinkedIn', - url: configuration.LINKED_IN_URL, - icon: , - screenReaderText: 'Follow edX on LinkedIn', - }, - { - title: 'Reddit', - url: configuration.REDDIT_URL, - icon: , - screenReaderText: 'Subscribe to the edX subreddit', - }, - ]; - - return ( -
- -
- - - - - -
- -
- ); -} - -const IntlPageContent = injectIntl(PageContent); - -class App extends Component { - componentDidMount() { - const { username } = this.props; - this.props.fetchUserAccount(username); - } - - render() { - return ( - - - - - - - - - - ); - } -} - -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); diff --git a/src/components/App.messages.jsx b/src/components/App.messages.jsx deleted file mode 100644 index 82d34e5..0000000 --- a/src/components/App.messages.jsx +++ /dev/null @@ -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; diff --git a/src/data/configureStore.js b/src/data/configureStore.js new file mode 100644 index 0000000..7375fa9 --- /dev/null +++ b/src/data/configureStore.js @@ -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; +} diff --git a/src/data/reducers.js b/src/data/reducers.js new file mode 100755 index 0000000..11c9666 --- /dev/null +++ b/src/data/reducers.js @@ -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; diff --git a/src/sagas.js b/src/data/sagas.js similarity index 64% rename from src/sagas.js rename to src/data/sagas.js index 5f68d5f..b8a2599 100644 --- a/src/sagas.js +++ b/src/data/sagas.js @@ -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()]); diff --git a/src/environment.js b/src/environment.js deleted file mode 100644 index 27ef068..0000000 --- a/src/environment.js +++ /dev/null @@ -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 = {}; diff --git a/src/index.jsx b/src/index.jsx index 40f5f8a..12a3760 100755 --- a/src/index.jsx +++ b/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( + +
+
+ + + + + +
+