diff --git a/.env.development b/.env.development index 7109e5d14c..9b907ba82b 100644 --- a/.env.development +++ b/.env.development @@ -1,6 +1,6 @@ NODE_ENV='development' ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' -BASE_URL='localhost:2001' +BASE_URL='http://localhost:2001' CREDENTIALS_BASE_URL='http://localhost:18150' CSRF_TOKEN_API_PATH='/csrf/api/v1/token' DISCOVERY_API_BASE_URL= @@ -40,7 +40,7 @@ ENABLE_NEW_VIDEO_UPLOAD_PAGE = false ENABLE_NEW_SCHEDULE_DETAILS_PAGE = false ENABLE_NEW_GRADING_PAGE = false ENABLE_NEW_COURSE_TEAM_PAGE = false -ENABLE_NEW_ADVANCED_SETTINGS_PAGE = false +ENABLE_NEW_ADVANCED_SETTINGS_PAGE = true ENABLE_NEW_IMPORT_PAGE = false ENABLE_NEW_EXPORT_PAGE = false ENABLE_UNIT_PAGE = false diff --git a/.env.test b/.env.test index 0e1a0b2c10..bebc3369d8 100644 --- a/.env.test +++ b/.env.test @@ -1,5 +1,5 @@ ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload' -BASE_URL='localhost:2001' +BASE_URL='http://localhost:2001' CREDENTIALS_BASE_URL='http://localhost:18150' CSRF_TOKEN_API_PATH='/csrf/api/v1/token' DISCOVERY_API_BASE_URL='http://localhost:18381' diff --git a/.eslintrc.js b/.eslintrc.js index 07325067f2..58835cd2a9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,7 @@ const { createConfig } = require('@edx/frontend-build'); module.exports = createConfig( -'eslint', + 'eslint', { rules: { 'jsx-a11y/label-has-associated-control': [2, { @@ -10,7 +10,7 @@ module.exports = createConfig( }], 'template-curly-spacing': 'off', 'react-hooks/exhaustive-deps': 'off', - indent: 'off', + indent: ['error', 2], 'no-restricted-exports': 'off', }, }, diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000000..43148337a9 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,34 @@ +{ + "extends": ["@edx/stylelint-config-edx"], + "rules": { + "selector-pseudo-class-no-unknown": [true, { + "ignorePseudoClasses": ["export"] + }], + "unit-no-unknown": [true, { + "ignoreUnits": ["\\.5"] + }], + "property-no-vendor-prefix": [true, { + "ignoreProperties": ["animation", "filter"] + }], + "value-no-vendor-prefix": [true, { + "ignoreValues": ["fill-available"] + }], + "function-no-unknown": null, + "number-leading-zero": "never", + "no-descending-specificity": null, + "selector-class-pattern": null, + "scss/no-global-function-names": null, + "color-hex-case": "upper", + "color-hex-length": "long", + "scss/dollar-variable-empty-line-before": null, + "scss/dollar-variable-colon-space-after": "at-least-one-space", + "at-rule-no-unknown": null, + "scss/at-rule-no-unknown": true, + "scss/at-import-partial-extension": null, + "scss/comment-no-empty": null, + "property-no-unknown": [true, { + "ignoreProperties": ["xs", "sm", "md", "lg", "xl", "xxl"] + }], + "alpha-value-notation": "number" + } +} diff --git a/package-lock.json b/package-lock.json index 3c7da83069..f2eed40cff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "react-responsive": "8.1.0", "react-router": "5.2.0", "react-router-dom": "5.2.0", + "react-textarea-autosize": "^8.4.1", "react-transition-group": "4.4.1", "redux": "4.0.5", "regenerator-runtime": "0.13.7", @@ -45,6 +46,7 @@ "@edx/browserslist-config": "1.0.0", "@edx/frontend-build": "12.8.6", "@edx/reactifex": "^1.0.3", + "@edx/stylelint-config-edx": "^2.3.0", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "12.1.1", "@testing-library/user-event": "^13.2.1", @@ -2026,6 +2028,22 @@ "@csstools/css-tokenizer": "^2.0.0" } }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "dev": true, @@ -2671,6 +2689,18 @@ "edx_reactifex": "main.js" } }, + "node_modules/@edx/stylelint-config-edx": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@edx/stylelint-config-edx/-/stylelint-config-edx-2.3.0.tgz", + "integrity": "sha512-JWOIHJmTm7JWWln6+aT2v7XCLuFZJ2cBDJrT6CgBDVSLaQbBJUN67DP5QO2rP9Z7fVKXlgZ0iJnP6IlniXIU2A==", + "dev": true, + "dependencies": { + "stylelint": "^14.5.0", + "stylelint-config-recommended-scss": "^5.0.2", + "stylelint-config-standard": "^25.0.0", + "stylelint-scss": "^4.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.4.1", "dev": true, @@ -4834,6 +4864,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, "node_modules/@types/node": { "version": "18.0.0", "dev": true, @@ -5750,6 +5786,15 @@ "get-intrinsic": "^1.1.3" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/assert-ok": { "version": "1.0.0", "license": "MIT" @@ -5767,6 +5812,15 @@ "dev": true, "license": "ISC" }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -6610,6 +6664,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "dev": true, @@ -7315,6 +7395,15 @@ "postcss": "^8.0.9" } }, + "node_modules/css-functions-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.1.0.tgz", + "integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==", + "dev": true, + "engines": { + "node": ">=12.22" + } + }, "node_modules/css-loader": { "version": "5.2.7", "dev": true, @@ -7597,6 +7686,31 @@ "node": ">=0.10.0" } }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decimal.js": { "version": "10.4.3", "dev": true, @@ -10519,6 +10633,12 @@ "node": ">=0.10.0" } }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true + }, "node_modules/gopd": { "version": "1.0.1", "dev": true, @@ -10565,6 +10685,15 @@ "dev": true, "license": "MIT" }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/harmony-reflect": { "version": "1.6.2", "dev": true, @@ -10818,6 +10947,18 @@ "node": ">= 12" } }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/html-webpack-plugin": { "version": "5.5.0", "dev": true, @@ -11229,6 +11370,15 @@ "node": ">=4" } }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/import-local": { "version": "3.1.0", "dev": true, @@ -14297,6 +14447,12 @@ "node": ">= 8" } }, + "node_modules/known-css-properties": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", + "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "dev": true + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "dev": true, @@ -14459,6 +14615,12 @@ "version": "4.1.1", "license": "MIT" }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "dev": true, @@ -14560,6 +14722,18 @@ "node": ">=0.10.0" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/map-visit": { "version": "1.0.0", "dev": true, @@ -14578,6 +14752,16 @@ "css-mediaquery": "^0.1.2" } }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "dev": true, @@ -14602,6 +14786,113 @@ "node": ">= 4.0.0" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "dev": true, @@ -14744,6 +15035,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minipass": { "version": "3.3.4", "dev": true, @@ -16194,6 +16508,12 @@ "dev": true, "license": "ISC" }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, "node_modules/postcss-merge-longhand": { "version": "5.1.7", "dev": true, @@ -16510,6 +16830,12 @@ "postcss": "^8.2.15" } }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==", + "dev": true + }, "node_modules/postcss-rtlcss": { "version": "3.7.2", "dev": true, @@ -16524,6 +16850,44 @@ "postcss": "^8.0.0" } }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.6.tgz", + "integrity": "sha512-rLDPhJY4z/i4nVFZ27j9GqLxj1pwxE80eAzUNRMXtcpipFYIeowerzBgG3yJhMtObGEXidtIgbUpQ3eLDsf5OQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.19" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.11", "dev": true, @@ -16889,6 +17253,15 @@ ], "license": "MIT" }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/raf": { "version": "3.4.1", "dev": true, @@ -17651,6 +18024,22 @@ "object-assign": "^4.1.1" } }, + "node_modules/react-textarea-autosize": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.4.1.tgz", + "integrity": "sha512-aD2C+qK6QypknC+lCMzteOdIjoMbNlgSFmJjCV+DrfTPwp59i/it9mMNf2HDzvRjQgKAyBDPyLJhcrzElf2U4Q==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-transition-group": { "version": "4.4.1", "license": "BSD-3-Clause", @@ -19041,6 +19430,56 @@ "node": ">=6" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/snapdragon": { "version": "0.8.2", "dev": true, @@ -19733,6 +20172,12 @@ "version": "4.0.0", "license": "MIT" }, + "node_modules/style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==", + "dev": true + }, "node_modules/stylehacks": { "version": "5.1.1", "dev": true, @@ -19748,6 +20193,187 @@ "postcss": "^8.2.15" } }, + "node_modules/stylelint": { + "version": "14.16.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", + "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "dev": true, + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^7.1.0", + "css-functions-list": "^3.1.0", + "debug": "^4.3.4", + "fast-glob": "^3.2.12", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^6.0.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.2.0", + "ignore": "^5.2.1", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.26.0", + "mathml-tag-names": "^2.1.3", + "meow": "^9.0.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.19", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "style-search": "^0.1.0", + "supports-hyperlinks": "^2.3.0", + "svg-tags": "^1.0.0", + "table": "^6.8.1", + "v8-compile-cache": "^2.3.0", + "write-file-atomic": "^4.0.2" + }, + "bin": { + "stylelint": "bin/stylelint.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", + "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-5.0.2.tgz", + "integrity": "sha512-b14BSZjcwW0hqbzm9b0S/ScN2+3CO3O4vcMNOw2KGf8lfVSwJ4p5TbNEXKwKl1+0FMtgRXZj6DqVUe/7nGnuBg==", + "dev": true, + "dependencies": { + "postcss-scss": "^4.0.2", + "stylelint-config-recommended": "^6.0.0", + "stylelint-scss": "^4.0.0" + }, + "peerDependencies": { + "stylelint": "^14.0.0" + } + }, + "node_modules/stylelint-config-standard": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz", + "integrity": "sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==", + "dev": true, + "dependencies": { + "stylelint-config-recommended": "^7.0.0" + }, + "peerDependencies": { + "stylelint": "^14.4.0" + } + }, + "node_modules/stylelint-config-standard/node_modules/stylelint-config-recommended": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz", + "integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==", + "dev": true, + "peerDependencies": { + "stylelint": "^14.4.0" + } + }, + "node_modules/stylelint-scss": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", + "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "dev": true, + "dependencies": { + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "stylelint": "^14.5.1 || ^15.0.0" + } + }, + "node_modules/stylelint/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true + }, + "node_modules/stylelint/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylelint/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylelint/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/superagent": { "version": "3.8.3", "dev": true, @@ -19847,6 +20473,12 @@ "dev": true, "license": "MIT" }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/svgo": { "version": "2.8.0", "dev": true, @@ -19954,6 +20586,44 @@ "version": "5.3.3", "license": "MIT" }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "dev": true, @@ -20307,6 +20977,15 @@ "node": ">=8" } }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "dev": true, @@ -20689,6 +21368,43 @@ } } }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sidecar": { "version": "1.1.2", "license": "MIT", @@ -20734,6 +21450,12 @@ "uuid": "bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "7.1.2", "dev": true, diff --git a/package.json b/package.json index 7f9a5d6478..60505424a3 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,9 @@ "scripts": { "build": "fedx-scripts webpack", "i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null", - "lint": "fedx-scripts eslint --ext .js --ext .jsx .", - "lint:fix": "fedx-scripts eslint --ext .js --ext .jsx . --fix", + "stylelint": "stylelint \"src/**/*.scss\" \"scss/**/*.scss\" --config .stylelintrc.json", + "lint": "npm run stylelint && fedx-scripts eslint --ext .js --ext .jsx .", + "lint:fix": "npm run stylelint && fedx-scripts eslint --ext .js --ext .jsx . --fix", "snapshot": "fedx-scripts jest --updateSnapshot", "start": "fedx-scripts webpack-dev-server --progress", "test": "fedx-scripts jest --coverage --passWithNoTests" @@ -59,6 +60,7 @@ "react-responsive": "8.1.0", "react-router": "5.2.0", "react-router-dom": "5.2.0", + "react-textarea-autosize": "^8.4.1", "react-transition-group": "4.4.1", "redux": "4.0.5", "regenerator-runtime": "0.13.7", @@ -69,6 +71,7 @@ "@edx/browserslist-config": "1.0.0", "@edx/frontend-build": "12.8.6", "@edx/reactifex": "^1.0.3", + "@edx/stylelint-config-edx": "^2.3.0", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "12.1.1", "@testing-library/user-event": "^13.2.1", diff --git a/src/CourseAuthoringPage.jsx b/src/CourseAuthoringPage.jsx index 355709c937..dea6503c8e 100644 --- a/src/CourseAuthoringPage.jsx +++ b/src/CourseAuthoringPage.jsx @@ -76,7 +76,7 @@ const CourseAuthoringPage = ({ courseId, children }) => { } return (
- {/* While V2 Editors are tempoarily served from thier own pages + {/* While V2 Editors are temporarily served from their own pages using url pattern containing /editor/, we shouldn't have the header and footer on these pages. This functionality will be removed in TNL-9591 */} @@ -89,7 +89,7 @@ const CourseAuthoringPage = ({ courseId, children }) => { courseId={courseId} /> ) - )} + )} {children} {!inProgress && showHeader && }
diff --git a/src/CourseAuthoringPage.test.jsx b/src/CourseAuthoringPage.test.jsx index d2f74e97f6..3e982c5929 100644 --- a/src/CourseAuthoringPage.test.jsx +++ b/src/CourseAuthoringPage.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { queryByTestId, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { getConfig, initializeMockApp } from '@edx/frontend-platform'; import MockAdapter from 'axios-mock-adapter'; @@ -23,51 +23,6 @@ jest.mock('react-router-dom', () => ({ })); let axiosMock; let store; -let container; -function renderComponent() { - const wrapper = render( - - - - - - - - , - ); - container = wrapper.container; -} - -const mockStore = async () => { - const apiBaseUrl = getConfig().STUDIO_BASE_URL; - const courseAppsApiUrl = `${apiBaseUrl}/api/course_apps/v1/apps`; - axiosMock.onGet(`${courseAppsApiUrl}/${courseId}`).reply(403, { - response: { status: 403 }, - }); - - await executeThunk(fetchCourseApps(courseId), store.dispatch); -}; -describe('DiscussionsSettings', () => { - beforeEach(() => { - initializeMockApp({ - authenticatedUser: { - userId: 3, - username: 'abc123', - administrator: true, - roles: [], - }, - }); - - store = initializeStore(); - axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - }); - - test('renders permission error in case of 403', async () => { - await mockStore(); - renderComponent(); - expect(queryByTestId(container, 'permissionDeniedAlert')).toBeInTheDocument(); - }); -}); describe('Editor Pages Load no header', () => { const mockStoreSuccess = async () => { diff --git a/src/CourseAuthoringRoutes.jsx b/src/CourseAuthoringRoutes.jsx index 6f08a711ee..5bd24937a8 100644 --- a/src/CourseAuthoringRoutes.jsx +++ b/src/CourseAuthoringRoutes.jsx @@ -9,6 +9,7 @@ import ProctoredExamSettings from './proctored-exam-settings/ProctoredExamSettin import EditorContainer from './editors/EditorContainer'; import VideoSelectorContainer from './selectors/VideoSelectorContainer'; import CustomPages from './custom-pages'; +import { AdvancedSettings } from './advanced-settings'; /** * As of this writing, these routes are mounted at a path prefixed with the following: @@ -34,25 +35,25 @@ const CourseAuthoringRoutes = ({ courseId }) => { {process.env.ENABLE_NEW_COURSE_OUTLINE_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_UPDATES_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_FILES_UPLOADS_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_VIDEO_UPLOAD_PAGE === 'true' && ( - + )} @@ -64,65 +65,65 @@ const CourseAuthoringRoutes = ({ courseId }) => { {process.env.ENABLE_NEW_CUSTOM_PAGES === 'true' && ( - + )} {process.env.ENABLE_UNIT_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_EDITOR_PAGES === 'true' && ( - + )} {process.env.ENABLE_NEW_EDITOR_PAGES === 'true' && ( - + )} {process.env.ENABLE_NEW_SCHEDULE_DETAILS_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_GRADING_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_COURSE_TEAM_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_ADVANCED_SETTINGS_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_IMPORT_PAGE === 'true' && ( - + )} {process.env.ENABLE_NEW_EXPORT_PAGE === 'true' && ( - + )} diff --git a/src/advanced-settings/AdvancedSettings.jsx b/src/advanced-settings/AdvancedSettings.jsx new file mode 100644 index 0000000000..3521f1b77b --- /dev/null +++ b/src/advanced-settings/AdvancedSettings.jsx @@ -0,0 +1,277 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; +import { + Container, Button, Layout, StatefulButton, +} from '@edx/paragon'; +import { CheckCircle, Info, Warning } from '@edx/paragon/icons'; +import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import AlertProctoringError from '../generic/AlertProctoringError'; +import InternetConnectionAlert from '../generic/internet-connection-alert'; +import { parseArrayOrObjectValues } from '../utils'; +import { RequestStatus } from '../data/constants'; +import SubHeader from '../generic/sub-header/SubHeader'; +import AlertMessage from '../generic/alert-message'; +import { fetchCourseAppSettings, updateCourseAppSetting, fetchProctoringExamErrors } from './data/thunks'; +import { + getCourseAppSettings, getSavingStatus, getProctoringExamErrors, getSendRequestErrors, getLoadingStatus, +} from './data/selectors'; +import SettingCard from './setting-card/SettingCard'; +import SettingsSidebar from './settings-sidebar/SettingsSidebar'; +import validateAdvancedSettingsData from './utils'; +import messages from './messages'; +import ModalError from './modal-error/ModalError'; + +const AdvancedSettings = ({ intl, courseId }) => { + const advancedSettingsData = useSelector(getCourseAppSettings); + const savingStatus = useSelector(getSavingStatus); + const proctoringExamErrors = useSelector(getProctoringExamErrors); + const settingsWithSendErrors = useSelector(getSendRequestErrors) || {}; + const dispatch = useDispatch(); + const [saveSettingsPrompt, showSaveSettingsPrompt] = useState(false); + const [showDeprecated, setShowDeprecated] = useState(false); + const [errorModal, showErrorModal] = useState(false); + const [editedSettings, setEditedSettings] = useState({}); + const [errorFields, setErrorFields] = useState([]); + const [showSuccessAlert, setShowSuccessAlert] = useState(false); + const loadingSettingsStatus = useSelector(getLoadingStatus); + const [isQueryPending, setIsQueryPending] = useState(false); + const [isEditableState, setIsEditableState] = useState(false); + const [hasInternetConnectionError, setInternetConnectionError] = useState(false); + const isLoading = loadingSettingsStatus === RequestStatus.IN_PROGRESS; + const updateSettingsButtonState = { + labels: { + default: intl.formatMessage(messages.buttonSaveText), + pending: intl.formatMessage(messages.buttonSavingText), + }, + disabledStates: ['pending'], + }; + + useEffect(() => { + dispatch(fetchCourseAppSettings(courseId)); + dispatch(fetchProctoringExamErrors(courseId)); + }, [courseId]); + + useEffect(() => { + if (savingStatus === RequestStatus.SUCCESSFUL) { + setIsQueryPending(false); + setShowSuccessAlert(true); + window.scrollTo({ top: 0, behavior: 'smooth' }); + + if (!isEditableState) { + showSaveSettingsPrompt(false); + } + } else if (savingStatus === RequestStatus.FAILED && !hasInternetConnectionError) { + setErrorFields(settingsWithSendErrors); + showErrorModal(true); + } + }, [savingStatus]); + + if (isLoading) { + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>; + } + + const handleSettingChange = (e, settingName) => { + const { value } = e.target; + if (!saveSettingsPrompt) { + showSaveSettingsPrompt(true); + } + setIsEditableState(true); + setShowSuccessAlert(false); + setEditedSettings((prevEditedSettings) => ({ + ...prevEditedSettings, + [settingName]: value, + })); + }; + + const handleResetSettingsValues = () => { + setIsEditableState(false); + showErrorModal(false); + setEditedSettings({}); + showSaveSettingsPrompt(false); + setInternetConnectionError(false); + setIsQueryPending(false); + }; + + const handleSettingBlur = () => { + validateAdvancedSettingsData(editedSettings, setErrorFields, setEditedSettings); + }; + + const handleUpdateAdvancedSettingsData = () => { + const isValid = validateAdvancedSettingsData(editedSettings, setErrorFields, setEditedSettings); + if (isValid) { + setIsQueryPending(true); + setIsEditableState(false); + } else { + setIsQueryPending(false); + showSaveSettingsPrompt(false); + showErrorModal(!errorModal); + } + }; + + const handleInternetConnectionFailed = () => { + setInternetConnectionError(true); + showSaveSettingsPrompt(false); + setShowSuccessAlert(false); + setIsQueryPending(false); + }; + + const handleQueryProcessing = () => { + setShowSuccessAlert(false); + dispatch(updateCourseAppSetting(courseId, parseArrayOrObjectValues(editedSettings))); + }; + + const handleManuallyChangeClick = (setToState) => { + setIsEditableState(true); + showErrorModal(setToState); + showSaveSettingsPrompt(true); + setIsQueryPending(false); + }; + + return ( + <> + +
+ {(proctoringExamErrors?.length > 0) && ( +
+
+ + +
+
+
+ Warning: }} + /> + )} + /> +
+ +
+
    + {Object.keys(advancedSettingsData).sort().map((settingName) => { + const settingData = advancedSettingsData[settingName]; + const editedValue = editedSettings[settingName] !== undefined + ? editedSettings[settingName] : JSON.stringify(settingData.value, null, 4); + + return ( + handleSettingChange(e, settingName)} + showDeprecated={showDeprecated} + name={settingName} + value={editedValue} + handleBlur={handleSettingBlur} + /> + ); + })} +
+
+
+
+
+ + + +
+
+
+
+ {!isEditableState && ( + + )} + + {intl.formatMessage(messages.buttonCancelText)} + + ), + , + ].filter(Boolean)} + variant="warning" + icon={Warning} + title={intl.formatMessage(messages.alertWarning)} + description={intl.formatMessage(messages.alertWarningDescriptions)} + /> +
+ handleManuallyChangeClick(setToState)} + handleUndoChanges={handleResetSettingsValues} + settingsData={advancedSettingsData} + errorList={errorFields.length > 0 ? errorFields : []} + /> + + ); +}; + +AdvancedSettings.propTypes = { + intl: intlShape.isRequired, + courseId: PropTypes.string.isRequired, +}; + +export default injectIntl(AdvancedSettings); diff --git a/src/advanced-settings/AdvancedSettings.test.jsx b/src/advanced-settings/AdvancedSettings.test.jsx new file mode 100644 index 0000000000..a4055c7b9e --- /dev/null +++ b/src/advanced-settings/AdvancedSettings.test.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import { initializeMockApp } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; +import { AppProvider } from '@edx/frontend-platform/react'; +import { render, fireEvent, waitFor } from '@testing-library/react'; +import MockAdapter from 'axios-mock-adapter'; + +import initializeStore from '../store'; +import { advancedSettingsMock } from './__mocks__'; +import { getCourseAdvancedSettingsApiUrl } from './data/api'; +import AdvancedSettings from './AdvancedSettings'; +import messages from './messages'; + +let axiosMock; +let store; +const mockPathname = '/foo-bar'; +const courseId = '123'; + +// Mock the TextareaAutosize component +jest.mock('react-textarea-autosize', () => jest.fn((props) => ( +