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: }}
+ />
+ )}
+ />
+
+ setShowDeprecated(!showDeprecated)}
+ size="sm"
+ >
+
+
+
+
+ {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) => (
+