diff --git a/.eslintrc.js b/.eslintrc.js
index b47d9043..5342ba97 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -8,6 +8,12 @@ module.exports = createConfig('eslint', {
webpack: {
config: path.resolve(__dirname, 'webpack.dev.config.js'),
},
+ alias: {
+ map: [
+ ['@communications-app', '.'],
+ ],
+ extensions: ['.ts', '.js', '.jsx', '.json'],
+ },
},
},
rules: {
diff --git a/package-lock.json b/package-lock.json
index 5366aaf2..6193e36d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,8 +22,14 @@
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0",
"@loadable/component": "^5.15.3",
+ "@openedx-plugins/communications-app-body-email-form": "file:plugins/communications-app/BodyForm",
"@openedx-plugins/communications-app-check-box-form": "file:plugins/communications-app/CheckBoxForm",
"@openedx-plugins/communications-app-input-form": "file:plugins/communications-app/InputForm",
+ "@openedx-plugins/communications-app-instructions-pro-freading": "file:plugins/communications-app/InstructionsProfreading",
+ "@openedx-plugins/communications-app-recipients-checks": "file:plugins/communications-app/RecipientsForm",
+ "@openedx-plugins/communications-app-schedule-section": "file:plugins/communications-app/ScheduleSection",
+ "@openedx-plugins/communications-app-subject-form": "file:plugins/communications-app/SubjectForm",
+ "@openedx-plugins/communications-app-task-alert-modal": "file:plugins/communications-app/TaskAlertModalForm",
"@openedx-plugins/communications-app-test-component": "file:plugins/communications-app/TestComponent",
"@tinymce/tinymce-react": "3.14.0",
"axios": "0.27.2",
@@ -50,7 +56,9 @@
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"axios-mock-adapter": "1.21.2",
+ "eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-webpack": "^0.13.8",
+ "eslint-plugin-import": "^2.29.1",
"glob": "7.2.3",
"husky": "7.0.4",
"jest": "27.5.1",
@@ -174,14 +182,6 @@
"eslint": "^7.5.0 || ^8.0.0"
}
},
- "node_modules/@babel/eslint-parser/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/generator": {
"version": "7.22.5",
"license": "MIT",
@@ -231,14 +231,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz",
@@ -261,14 +253,6 @@
"@babel/core": "^7.0.0"
}
},
- "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz",
@@ -285,14 +269,6 @@
"@babel/core": "^7.0.0"
}
},
- "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/@babel/helper-define-polyfill-provider": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz",
@@ -2317,6 +2293,14 @@
"version": "1.1.4",
"license": "MIT"
},
+ "node_modules/@edx/frontend-build/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
"node_modules/@edx/frontend-build/node_modules/diff-sequences": {
"version": "26.6.2",
"license": "MIT",
@@ -2324,6 +2308,17 @@
"node": ">= 10.14.2"
}
},
+ "node_modules/@edx/frontend-build/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/@edx/frontend-build/node_modules/emittery": {
"version": "0.7.2",
"license": "MIT",
@@ -2334,6 +2329,34 @@
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
}
},
+ "node_modules/@edx/frontend-build/node_modules/eslint-plugin-import": {
+ "version": "2.27.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
+ "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "array.prototype.flatmap": "^1.3.1",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.7.4",
+ "has": "^1.0.3",
+ "is-core-module": "^2.11.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.values": "^1.1.6",
+ "resolve": "^1.22.1",
+ "semver": "^6.3.0",
+ "tsconfig-paths": "^3.14.1"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8"
+ }
+ },
"node_modules/@edx/frontend-build/node_modules/execa": {
"version": "4.1.0",
"license": "MIT",
@@ -5463,6 +5486,10 @@
"node": ">= 8"
}
},
+ "node_modules/@openedx-plugins/communications-app-body-email-form": {
+ "resolved": "plugins/communications-app/BodyForm",
+ "link": true
+ },
"node_modules/@openedx-plugins/communications-app-check-box-form": {
"resolved": "plugins/communications-app/CheckBoxForm",
"link": true
@@ -5471,6 +5498,26 @@
"resolved": "plugins/communications-app/InputForm",
"link": true
},
+ "node_modules/@openedx-plugins/communications-app-instructions-pro-freading": {
+ "resolved": "plugins/communications-app/InstructionsProfreading",
+ "link": true
+ },
+ "node_modules/@openedx-plugins/communications-app-recipients-checks": {
+ "resolved": "plugins/communications-app/RecipientsForm",
+ "link": true
+ },
+ "node_modules/@openedx-plugins/communications-app-schedule-section": {
+ "resolved": "plugins/communications-app/ScheduleSection",
+ "link": true
+ },
+ "node_modules/@openedx-plugins/communications-app-subject-form": {
+ "resolved": "plugins/communications-app/SubjectForm",
+ "link": true
+ },
+ "node_modules/@openedx-plugins/communications-app-task-alert-modal": {
+ "resolved": "plugins/communications-app/TaskAlertModalForm",
+ "link": true
+ },
"node_modules/@openedx-plugins/communications-app-test-component": {
"resolved": "plugins/communications-app/TestComponent",
"link": true
@@ -6269,7 +6316,8 @@
},
"node_modules/@types/json5": {
"version": "0.0.29",
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"node_modules/@types/mime": {
"version": "1.3.3",
@@ -6852,13 +6900,14 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/array-includes": {
- "version": "3.1.6",
- "license": "MIT",
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz",
+ "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==",
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1",
"is-string": "^1.0.7"
},
"engines": {
@@ -6907,13 +6956,32 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz",
+ "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/array.prototype.flat": {
- "version": "1.3.1",
- "license": "MIT",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz",
+ "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==",
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
},
"engines": {
@@ -6924,12 +6992,13 @@
}
},
"node_modules/array.prototype.flatmap": {
- "version": "1.3.1",
- "license": "MIT",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz",
+ "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==",
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
"es-shim-unscopables": "^1.0.0"
},
"engines": {
@@ -7445,14 +7514,6 @@
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
- "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
- "version": "6.3.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz",
@@ -9496,18 +9557,32 @@
"eslint-plugin-import": "^2.25.2"
}
},
+ "node_modules/eslint-import-resolver-alias": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-alias/-/eslint-import-resolver-alias-1.1.2.tgz",
+ "integrity": "sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ },
+ "peerDependencies": {
+ "eslint-plugin-import": ">=1.4.0"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
- "version": "0.3.7",
- "license": "MIT",
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
"dependencies": {
"debug": "^3.2.7",
- "is-core-module": "^2.11.0",
- "resolve": "^1.22.1"
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
}
},
"node_modules/eslint-import-resolver-node/node_modules/debug": {
"version": "3.2.7",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
@@ -9628,24 +9703,27 @@
}
},
"node_modules/eslint-plugin-import": {
- "version": "2.27.5",
- "license": "MIT",
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "array.prototype.flatmap": "^1.3.1",
+ "version": "2.29.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
+ "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
+ "dependencies": {
+ "array-includes": "^3.1.7",
+ "array.prototype.findlastindex": "^1.2.3",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
"debug": "^3.2.7",
"doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.7",
- "eslint-module-utils": "^2.7.4",
- "has": "^1.0.3",
- "is-core-module": "^2.11.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.8.0",
+ "hasown": "^2.0.0",
+ "is-core-module": "^2.13.1",
"is-glob": "^4.0.3",
"minimatch": "^3.1.2",
- "object.values": "^1.1.6",
- "resolve": "^1.22.1",
- "semver": "^6.3.0",
- "tsconfig-paths": "^3.14.1"
+ "object.fromentries": "^2.0.7",
+ "object.groupby": "^1.0.1",
+ "object.values": "^1.1.7",
+ "semver": "^6.3.1",
+ "tsconfig-paths": "^3.15.0"
},
"engines": {
"node": ">=4"
@@ -16919,12 +16997,13 @@
}
},
"node_modules/object.fromentries": {
- "version": "2.0.6",
- "license": "MIT",
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz",
+ "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==",
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
},
"engines": {
"node": ">= 0.4"
@@ -16933,6 +17012,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/object.groupby": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz",
+ "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==",
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1",
+ "get-intrinsic": "^1.2.1"
+ }
+ },
"node_modules/object.hasown": {
"version": "1.1.2",
"license": "MIT",
@@ -16955,12 +17045,13 @@
}
},
"node_modules/object.values": {
- "version": "1.1.6",
- "license": "MIT",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz",
+ "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==",
"dependencies": {
"call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
+ "define-properties": "^1.2.0",
+ "es-abstract": "^1.22.1"
},
"engines": {
"node": ">= 0.4"
@@ -19148,10 +19239,11 @@
"license": "MIT"
},
"node_modules/resolve": {
- "version": "1.22.2",
- "license": "MIT",
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": {
- "is-core-module": "^2.11.0",
+ "is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -19720,8 +19812,9 @@
}
},
"node_modules/semver": {
- "version": "6.3.0",
- "license": "ISC",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
@@ -21189,8 +21282,9 @@
}
},
"node_modules/tsconfig-paths": {
- "version": "3.14.2",
- "license": "MIT",
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
"dependencies": {
"@types/json5": "^0.0.29",
"json5": "^1.0.2",
@@ -21200,7 +21294,8 @@
},
"node_modules/tsconfig-paths/node_modules/json5": {
"version": "1.0.2",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dependencies": {
"minimist": "^1.2.0"
},
@@ -21210,7 +21305,8 @@
},
"node_modules/tsconfig-paths/node_modules/strip-bom": {
"version": "3.0.0",
- "license": "MIT",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
"engines": {
"node": ">=4"
}
@@ -22409,6 +22505,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "plugins/communications-app/BodyForm": {
+ "name": "@openedx-plugins/communications-app-body-email-form",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
"plugins/communications-app/CheckBoxForm": {
"name": "@openedx-plugins/communications-app-check-box-form",
"version": "1.0.0",
@@ -22441,6 +22553,100 @@
}
}
},
- "plugins/communications-app/TestComponent": {}
+ "plugins/communications-app/InstructionsProfreading": {
+ "name": "@openedx-plugins/communications-app-instructions-pro-freading",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
+ "plugins/communications-app/RecipientsForm": {
+ "name": "@openedx-plugins/communications-app-recipients-checks",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
+ "plugins/communications-app/ScheduleSection": {
+ "name": "@openedx-plugins/communications-app-schedule-section",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
+ "plugins/communications-app/SubjectForm": {
+ "name": "@openedx-plugins/communications-app-subject-form",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
+ "plugins/communications-app/TaskAlertModalForm": {
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ },
+ "plugins/communications-app/TestComponent": {
+ "name": "@openedx-plugins/communications-app-test-component",
+ "version": "1.0.0",
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+ }
}
}
diff --git a/package.json b/package.json
index a077a7da..b27e90dc 100644
--- a/package.json
+++ b/package.json
@@ -46,8 +46,14 @@
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0",
"@loadable/component": "^5.15.3",
+ "@openedx-plugins/communications-app-body-email-form": "file:plugins/communications-app/BodyForm",
"@openedx-plugins/communications-app-check-box-form": "file:plugins/communications-app/CheckBoxForm",
"@openedx-plugins/communications-app-input-form": "file:plugins/communications-app/InputForm",
+ "@openedx-plugins/communications-app-instructions-pro-freading": "file:plugins/communications-app/InstructionsProfreading",
+ "@openedx-plugins/communications-app-recipients-checks": "file:plugins/communications-app/RecipientsForm",
+ "@openedx-plugins/communications-app-schedule-section": "file:plugins/communications-app/ScheduleSection",
+ "@openedx-plugins/communications-app-subject-form": "file:plugins/communications-app/SubjectForm",
+ "@openedx-plugins/communications-app-task-alert-modal": "file:plugins/communications-app/TaskAlertModalForm",
"@openedx-plugins/communications-app-test-component": "file:plugins/communications-app/TestComponent",
"@tinymce/tinymce-react": "3.14.0",
"axios": "0.27.2",
@@ -74,7 +80,9 @@
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "12.1.5",
"axios-mock-adapter": "1.21.2",
+ "eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-webpack": "^0.13.8",
+ "eslint-plugin-import": "^2.29.1",
"glob": "7.2.3",
"husky": "7.0.4",
"jest": "27.5.1",
diff --git a/plugins/communications-app/BodyForm/index.jsx b/plugins/communications-app/BodyForm/index.jsx
new file mode 100644
index 00000000..d5569ede
--- /dev/null
+++ b/plugins/communications-app/BodyForm/index.jsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Form } from '@edx/paragon';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import TextEditor from '@communications-app/src/components/bulk-email-tool/text-editor/TextEditor';
+
+import messages from './messages';
+
+const BodyForm = ({ formState, setFormState }) => {
+ const intl = useIntl();
+ const { body, isFormSubmitted = false } = formState ?? {};
+ const handleChangeTextEditor = (value) => {
+ setFormState({ ...formState, body: { value } });
+ };
+
+ const bodyValidation = body.value.length > 0;
+
+ return (
+
+ {intl.formatMessage(messages.bodyFormFieldLabel)}
+
+ {isFormSubmitted && !bodyValidation && (
+
+ {intl.formatMessage(messages.bodyFormFieldError)}
+
+ )}
+
+ );
+};
+
+BodyForm.defaultProps = {
+ formState: {},
+ setFormState: () => {},
+};
+
+BodyForm.propTypes = {
+ formState: PropTypes.shape({}),
+ setFormState: PropTypes.func,
+};
+
+export default BodyForm;
diff --git a/plugins/communications-app/BodyForm/messages.js b/plugins/communications-app/BodyForm/messages.js
new file mode 100644
index 00000000..fcca334f
--- /dev/null
+++ b/plugins/communications-app/BodyForm/messages.js
@@ -0,0 +1,17 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* index.jsx Messages */
+ bodyFormFieldLabel: {
+ id: 'body.form.field.label',
+ defaultMessage: 'Body',
+ description: 'Email Body label. Meant to have colon or equivilant punctuation.',
+ },
+ bodyFormFieldError: {
+ id: 'body.form.field.error',
+ defaultMessage: 'The message cannot be blank',
+ description: 'An error message located under the body editor. Visible only on failure.',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/BodyForm/package.json b/plugins/communications-app/BodyForm/package.json
new file mode 100644
index 00000000..0ea35414
--- /dev/null
+++ b/plugins/communications-app/BodyForm/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-body-email-form",
+ "version": "1.0.0",
+ "description": "openedx body field in build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/plugins/communications-app/CheckBoxForm/package.json b/plugins/communications-app/CheckBoxForm/package.json
index 46647768..c975cc54 100644
--- a/plugins/communications-app/CheckBoxForm/package.json
+++ b/plugins/communications-app/CheckBoxForm/package.json
@@ -1,7 +1,7 @@
{
"name": "@openedx-plugins/communications-app-check-box-form",
"version": "1.0.0",
- "description": "edx input type checkbox form to use it in this mfe",
+ "description": "openedx input type checkbox form to use it in this mfe",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
diff --git a/plugins/communications-app/InputForm/index.jsx b/plugins/communications-app/InputForm/index.jsx
index 42be8579..d4b91a72 100644
--- a/plugins/communications-app/InputForm/index.jsx
+++ b/plugins/communications-app/InputForm/index.jsx
@@ -6,7 +6,12 @@ const InputForm = ({
}) => {
const feedbackType = isValid ? 'valid' : 'invalid';
return (
-
+
{label}
diff --git a/plugins/communications-app/InputForm/package.json b/plugins/communications-app/InputForm/package.json
index 8a9f94d1..f2c24b38 100644
--- a/plugins/communications-app/InputForm/package.json
+++ b/plugins/communications-app/InputForm/package.json
@@ -1,7 +1,7 @@
{
"name": "@openedx-plugins/communications-app-input-form",
"version": "1.0.0",
- "description": "edx input form to use it in this mfe",
+ "description": "openedx input form to use it in this mfe",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
diff --git a/plugins/communications-app/InstructionsProfreading/index.jsx b/plugins/communications-app/InstructionsProfreading/index.jsx
new file mode 100644
index 00000000..2e7eb9eb
--- /dev/null
+++ b/plugins/communications-app/InstructionsProfreading/index.jsx
@@ -0,0 +1,15 @@
+import React, { memo } from 'react';
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import messages from './messages';
+
+const InstructionsProofreading = () => {
+ const intl = useIntl();
+ return (
+
+
{intl.formatMessage(messages.instructionsProofreading)}
+
+ );
+};
+
+export default memo(InstructionsProofreading);
diff --git a/plugins/communications-app/InstructionsProfreading/messages.js b/plugins/communications-app/InstructionsProfreading/messages.js
new file mode 100644
index 00000000..5a2f0d7e
--- /dev/null
+++ b/plugins/communications-app/InstructionsProfreading/messages.js
@@ -0,0 +1,12 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* index.jsx Messages */
+ instructionsProofreading: {
+ id: 'instructions.proofreading',
+ defaultMessage: 'We recommend sending learners no more than one email message per week. Before you send your email, review the text carefully and send it to yourself first, so that you can preview the formatting and make sure embedded images and links work correctly.',
+ description: 'A set of instructions to give users a heads up about the formatting of the email they are about to send',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/InstructionsProfreading/package.json b/plugins/communications-app/InstructionsProfreading/package.json
new file mode 100644
index 00000000..128df89c
--- /dev/null
+++ b/plugins/communications-app/InstructionsProfreading/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-instructions-pro-freading",
+ "version": "1.0.0",
+ "description": "openedx instructions build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/plugins/communications-app/RecipientsForm/index.jsx b/plugins/communications-app/RecipientsForm/index.jsx
new file mode 100644
index 00000000..5bac3daf
--- /dev/null
+++ b/plugins/communications-app/RecipientsForm/index.jsx
@@ -0,0 +1,164 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import { Form } from '@edx/paragon';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+
+import './styles.scss';
+
+const disableIsHasLearners = ['track', 'cohort'];
+
+const RecipientsForm = ({ formState, setFormState }) => {
+ const {
+ isFormSubmitted, emailRecipients, isEditMode, cohorts: additionalCohorts,
+ } = formState ?? {};
+
+ const { value: emailRecipientsInitial } = emailRecipients;
+ const [selectedGroups, setSelectedGroups] = useState([]);
+ const hasAllLearnersSelected = selectedGroups.some((group) => group === 'learners');
+
+ const handleChangeCheckBoxes = ({ target: { value, checked } }) => {
+ const { value: emailRecipientsValue } = emailRecipients;
+
+ let newValue;
+
+ if (checked) {
+ const uniqueSet = new Set([...emailRecipientsValue, value]);
+ newValue = Array.from(uniqueSet);
+ } else {
+ newValue = emailRecipientsValue.filter((item) => item !== value);
+ }
+
+ if (checked && value === 'learners') {
+ newValue = newValue.filter(item => !disableIsHasLearners.some(disabled => item.includes(disabled)));
+ }
+
+ setFormState({ ...formState, emailRecipients: { ...emailRecipients, value: newValue } });
+ setSelectedGroups(newValue);
+ };
+
+ useEffect(() => {
+ setSelectedGroups(emailRecipientsInitial);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isEditMode]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ // additional cohorts
+ additionalCohorts
+ && additionalCohorts.map((cohort) => (
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+ { isFormSubmitted && selectedGroups.length === 0 && (
+
+
+
+ )}
+
+ );
+};
+
+RecipientsForm.defaultProps = {
+ formState: {},
+ setFormState: () => {},
+};
+
+RecipientsForm.propTypes = {
+ formState: PropTypes.shape({}),
+ setFormState: PropTypes.func,
+};
+
+export default RecipientsForm;
diff --git a/plugins/communications-app/RecipientsForm/package.json b/plugins/communications-app/RecipientsForm/package.json
new file mode 100644
index 00000000..2b033cca
--- /dev/null
+++ b/plugins/communications-app/RecipientsForm/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-recipients-checks",
+ "version": "1.0.0",
+ "description": "openedx recipients field in build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/plugins/communications-app/RecipientsForm/styles.scss b/plugins/communications-app/RecipientsForm/styles.scss
new file mode 100644
index 00000000..a8f104d1
--- /dev/null
+++ b/plugins/communications-app/RecipientsForm/styles.scss
@@ -0,0 +1,8 @@
+.recipient-groups {
+ > div {
+ padding-right: 0.5rem;
+ input {
+ padding: 0.5rem !important;
+ }
+ }
+ }
\ No newline at end of file
diff --git a/plugins/communications-app/ScheduleSection/index.jsx b/plugins/communications-app/ScheduleSection/index.jsx
new file mode 100644
index 00000000..af4c18c8
--- /dev/null
+++ b/plugins/communications-app/ScheduleSection/index.jsx
@@ -0,0 +1,206 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import {
+ StatefulButton,
+ Button,
+ Form,
+ Icon,
+ Toast,
+} from '@edx/paragon';
+import {
+ SpinnerSimple,
+ Cancel,
+ Send,
+ Event,
+ Check,
+} from '@edx/paragon/icons';
+import { getConfig } from '@edx/frontend-platform';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import ScheduleEmailForm from '@communications-app/src/components/bulk-email-tool/bulk-email-form/ScheduleEmailForm';
+import useMobileResponsive from '@communications-app/src/utils/useMobileResponsive';
+
+import messages from './messages';
+
+/*
+const formIconsState = {
+ default: ,
+ schedule: ,
+ reschedule: ,
+ pending: ,
+ complete: ,
+ completeSchedule: ,
+ error: ,
+}; */
+
+/*
+const formSubmitStateMessages = {
+ default: intl.formatMessage(messages.ScheduleSectionSubmitButtonDefault),
+ schedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonSchedule),
+ reschedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonReschedule),
+ pending: intl.formatMessage(messages.ScheduleSectionSubmitButtonPending),
+ complete: intl.formatMessage(messages.ScheduleSectionSubmitButtonComplete),
+ completeSchedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonCompleteSchedule),
+ error: intl.formatMessage(messages.ScheduleSectionSubmitButtonError)
+}; */
+
+const formStatusToast = ['error', 'complete', 'completeSchedule'];
+
+const ScheduleSection = ({ formState, setFormState, openTaskAlert }) => {
+ const intl = useIntl();
+ const isMobile = useMobileResponsive();
+ const [scheduleInputChanged, isScheduleInputChanged] = useState(false);
+ const {
+ isScheduled,
+ scheduleDate = '',
+ scheduleTime = '',
+ isEditMode,
+ formStatus,
+ isScheduledSubmitted = false,
+ } = formState ?? {};
+
+ const formStatusErrors = {
+ error: intl.formatMessage(messages.ScheduleSectionSubmitFormError),
+ complete: intl.formatMessage(messages.ScheduleSectionSubmitFormSuccess),
+ completeSchedule: intl.formatMessage(messages.ScheduleSectionSubmitFormScheduledSuccess),
+ };
+
+ const handleChangeScheduled = () => {
+ const newSchedule = !isScheduled;
+ const newFormStatus = newSchedule ? 'schedule' : 'default';
+ setFormState({ ...formState, formStatus: newFormStatus, isScheduled: newSchedule });
+ };
+
+ const handleScheduleDate = ({ target: { name, value } }) => {
+ setFormState({ ...formState, [name]: value });
+ if (!scheduleInputChanged) {
+ isScheduleInputChanged(true);
+ }
+ };
+
+ const scheduleFields = isScheduledSubmitted ? scheduleDate.length > 0 && scheduleTime.length > 0
+ && scheduleInputChanged : true;
+
+ const checkIsValidSchedule = isScheduled ? scheduleFields : true;
+
+ const handleResetFormValues = () => {
+ const { emailRecipients, subject, body } = formState ?? {};
+ const newRecipientsValue = { ...emailRecipients, value: [] };
+ const newSubjectValue = { ...subject, value: '' };
+ const newBodyValue = { ...body, value: '' };
+
+ setFormState({
+ ...formState,
+ emailRecipients: newRecipientsValue,
+ subject: newSubjectValue,
+ body: newBodyValue,
+ scheduleDate: '',
+ scheduleTime: '',
+ isScheduled: false,
+ isEditMode: false,
+ formStatus: 'default',
+ isScheduleButtonClicked: false,
+ isScheduledSubmitted: false,
+ });
+ };
+
+ return (
+
+ {getConfig().SCHEDULE_EMAIL_SECTION && (
+
+
+ {intl.formatMessage(messages.ScheduleSectionSubmitScheduleBox)}
+
+
+ )}
+
+ {isScheduled && (
+
+ )}
+
+
+
+ {isEditMode && (
+
+ )}
+
+ {
+ event.preventDefault();
+ if (formStatus === 'schedule' && !isScheduledSubmitted) {
+ setFormState({ ...formState, isScheduleButtonClicked: true });
+ }
+ openTaskAlert();
+ }}
+ state={formStatus}
+ icons={{
+ default: ,
+ schedule: ,
+ reschedule: ,
+ pending: ,
+ complete: ,
+ completeSchedule: ,
+ error: ,
+ }}
+ labels={{
+ default: intl.formatMessage(messages.ScheduleSectionSubmitButtonDefault),
+ schedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonSchedule),
+ reschedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonReschedule),
+ pending: intl.formatMessage(messages.ScheduleSectionSubmitButtonPending),
+ complete: intl.formatMessage(messages.ScheduleSectionSubmitButtonComplete),
+ completeSchedule: intl.formatMessage(messages.ScheduleSectionSubmitButtonCompleteSchedule),
+ error: intl.formatMessage(messages.ScheduleSectionSubmitButtonError),
+ }}
+ disabledStates={[
+ 'pending',
+ 'complete',
+ 'completeSchedule',
+ ]}
+ />
+
+ { setFormState({ ...formState, formStatus: 'default' }); }}
+ >
+ {formStatusErrors[formStatus] || null}
+
+
+
+ );
+};
+
+ScheduleSection.defaultProps = {
+ formState: {},
+ setFormState: () => {},
+ openTaskAlert: () => {},
+};
+
+ScheduleSection.propTypes = {
+ formState: PropTypes.shape({}),
+ setFormState: PropTypes.func,
+ openTaskAlert: PropTypes.func,
+};
+
+export default ScheduleSection;
diff --git a/plugins/communications-app/ScheduleSection/messages.js b/plugins/communications-app/ScheduleSection/messages.js
new file mode 100644
index 00000000..75baf818
--- /dev/null
+++ b/plugins/communications-app/ScheduleSection/messages.js
@@ -0,0 +1,53 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* index.jsx Messages */
+ ScheduleSectionSubmitScheduleBox: {
+ id: 'schedule.section.submit.scheduleBox',
+ defaultMessage: 'Schedule this email for a future date',
+ description: 'Checkbox to schedule sending the email at a later date',
+ },
+ ScheduleSectionSubmitButtonDefault: {
+ id: 'schedule.section.submit.button.default',
+ defaultMessage: 'Send email',
+ },
+ ScheduleSectionSubmitFormError: {
+ id: 'schedule.section.submit.error',
+ defaultMessage: 'An error occured while attempting to send the email.',
+ description: 'An Error message located under the submit button for the email form. Visible only on a failure.',
+ },
+ ScheduleSectionSubmitFormSuccess: {
+ id: 'schedule.section.form.success',
+ defaultMessage: 'Email successfully created',
+ },
+ ScheduleSectionSubmitFormScheduledSuccess: {
+ id: 'schedule.section.submit.scheduled.success',
+ defaultMessage: 'Email successfully scheduled',
+ },
+ ScheduleSectionSubmitButtonSchedule: {
+ id: 'schedule.section.submit.button.schedule',
+ defaultMessage: 'Schedule Email',
+ },
+ ScheduleSectionSubmitButtonReschedule: {
+ id: 'schedule.section.submit.button.reschedule',
+ defaultMessage: 'Reschedule Email',
+ },
+ ScheduleSectionSubmitButtonPending: {
+ id: 'schedule.section.submit.button.pending',
+ defaultMessage: 'Submitting',
+ },
+ ScheduleSectionSubmitButtonComplete: {
+ id: 'schedule.section.submit.button.send.complete',
+ defaultMessage: 'Email Created',
+ },
+ ScheduleSectionSubmitButtonError: {
+ id: 'schedule.section.submit.button.error',
+ defaultMessage: 'Error',
+ },
+ ScheduleSectionSubmitButtonCompleteSchedule: {
+ id: 'schedule.section.submit.button.schedule.complete',
+ defaultMessage: 'Scheduling Done',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/ScheduleSection/package.json b/plugins/communications-app/ScheduleSection/package.json
new file mode 100644
index 00000000..01e75963
--- /dev/null
+++ b/plugins/communications-app/ScheduleSection/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-schedule-section",
+ "version": "1.0.0",
+ "description": "openedx schedule fields in build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/plugins/communications-app/SubjectForm/index.jsx b/plugins/communications-app/SubjectForm/index.jsx
new file mode 100644
index 00000000..f44ad055
--- /dev/null
+++ b/plugins/communications-app/SubjectForm/index.jsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Form } from '@edx/paragon';
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import messages from './messages';
+
+const SubjectForm = ({ formState, setFormState }) => {
+ const intl = useIntl();
+ const { subject, isFormSubmitted } = formState ?? {};
+ const handleChangeEmailSubject = ({ target: { value } }) => {
+ setFormState({ ...formState, subject: { value } });
+ };
+
+ const subjectValidation = subject.value.length > 0;
+
+ return (
+
+ {intl.formatMessage(messages.bulkEmailSubjectLabel)}
+
+ { isFormSubmitted && !subjectValidation && (
+
+ {intl.formatMessage(messages.bulkEmailFormSubjectError)}
+
+ ) }
+
+ );
+};
+
+SubjectForm.defaultProps = {
+ formState: {},
+ setFormState: () => {},
+};
+
+SubjectForm.propTypes = {
+ formState: PropTypes.shape({}),
+ setFormState: PropTypes.func,
+};
+
+export default SubjectForm;
diff --git a/plugins/communications-app/SubjectForm/messages.js b/plugins/communications-app/SubjectForm/messages.js
new file mode 100644
index 00000000..caa065a2
--- /dev/null
+++ b/plugins/communications-app/SubjectForm/messages.js
@@ -0,0 +1,17 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* SubjectForm.jsx Messages */
+ bulkEmailSubjectLabel: {
+ id: 'bulk.email.subject.label',
+ defaultMessage: 'Subject',
+ description: 'Email subject line input label. Meant to have colon or equivilant punctuation.',
+ },
+ bulkEmailFormSubjectError: {
+ id: 'bulk.email.form.subject.error',
+ defaultMessage: 'A subject is required',
+ description: 'An Error message located under the subject line. Visible only on failure.',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/SubjectForm/package.json b/plugins/communications-app/SubjectForm/package.json
new file mode 100644
index 00000000..a172927c
--- /dev/null
+++ b/plugins/communications-app/SubjectForm/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-subject-form",
+ "version": "1.0.0",
+ "description": "openedx subject field in build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/plugins/communications-app/TaskAlertModalForm/AlertTypes.jsx b/plugins/communications-app/TaskAlertModalForm/AlertTypes.jsx
new file mode 100644
index 00000000..140fef3f
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/AlertTypes.jsx
@@ -0,0 +1,81 @@
+import PropTypes from 'prop-types';
+import { useIntl } from '@edx/frontend-platform/i18n';
+
+import messages from './messages.alerts';
+
+export const AlertMessage = ({ emailRecipients, isScheduled, subject }) => {
+ const intl = useIntl();
+ return (
+ <>
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesRecipients, { subject })}
+
+ {emailRecipients.map((group) => (
+ - {group}
+ ))}
+
+ {!isScheduled && (
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesInstructionCaption)}
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesInstructionCaptionMessage)}
+
+ )}
+ >
+ );
+};
+
+AlertMessage.defaultProps = {
+ emailRecipients: [],
+};
+
+AlertMessage.propTypes = {
+ emailRecipients: PropTypes.arrayOf(PropTypes.string),
+ isScheduled: PropTypes.bool.isRequired,
+ subject: PropTypes.string.isRequired,
+};
+
+export const EditMessage = ({
+ emailRecipients, isScheduled, scheduleDate, scheduleTime, subject,
+}) => {
+ const intl = useIntl();
+ return (
+ <>
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesEditingDate, {
+ dateTime: new Date(`${scheduleDate} ${scheduleTime}`).toLocaleString(),
+ })}
+
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesEditingSubject, {
+ subject,
+ })}
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesEditingTo)}
+
+ {emailRecipients.map((group) => (
+ - {group}
+ ))}
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesEditingWarning)}
+ {!isScheduled && (
+
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesInstructionCaption)}
+ {intl.formatMessage(messages.TaskAlertModalAlertTypesInstructionCaptionMessage)}
+
+ )}
+ >
+ );
+};
+
+EditMessage.defaultProps = {
+ emailRecipients: [],
+ scheduleDate: '',
+ scheduleTime: '',
+};
+
+EditMessage.propTypes = {
+ emailRecipients: PropTypes.arrayOf(PropTypes.string),
+ isScheduled: PropTypes.bool.isRequired,
+ scheduleDate: PropTypes.string,
+ scheduleTime: PropTypes.string,
+ subject: PropTypes.string.isRequired,
+};
diff --git a/plugins/communications-app/TaskAlertModalForm/api.js b/plugins/communications-app/TaskAlertModalForm/api.js
new file mode 100644
index 00000000..62fb078d
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/api.js
@@ -0,0 +1,25 @@
+import { getConfig } from '@edx/frontend-platform';
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { logError } from '@edx/frontend-platform/logging';
+
+export async function postBulkEmailInstructorTask(email, courseId) {
+ try {
+ const url = `${getConfig().LMS_BASE_URL}/courses/${courseId}/instructor/api/send_email`;
+ const response = await getAuthenticatedHttpClient().post(url, email);
+ return response;
+ } catch (error) {
+ logError(error);
+ throw new Error(error);
+ }
+}
+
+export async function patchScheduledBulkEmailInstructorTask(emailData, courseId, scheduleId) {
+ const endpointUrl = `${getConfig().LMS_BASE_URL}/api/instructor_task/v1/schedules/${courseId}/bulk_email/${scheduleId}`;
+ try {
+ const response = await getAuthenticatedHttpClient().patch(endpointUrl, emailData);
+ return response;
+ } catch (error) {
+ logError(error);
+ throw new Error(error);
+ }
+}
diff --git a/plugins/communications-app/TaskAlertModalForm/index.jsx b/plugins/communications-app/TaskAlertModalForm/index.jsx
new file mode 100644
index 00000000..52ccb031
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/index.jsx
@@ -0,0 +1,158 @@
+import React, { useCallback, useContext } from 'react';
+import PropTypes from 'prop-types';
+import TaskAlertModal from '@communications-app/src/components/bulk-email-tool/task-alert-modal';
+import { getScheduledBulkEmailThunk } from '@communications-app/src/components/bulk-email-tool/bulk-email-task-manager/bulk-email-scheduled-emails-table/data/thunks';
+import { BulkEmailContext } from '@communications-app/src/components/bulk-email-tool/bulk-email-context';
+
+import { postBulkEmailInstructorTask, patchScheduledBulkEmailInstructorTask } from './api';
+import { AlertMessage, EditMessage } from './AlertTypes';
+
+const TaskAlertModalForm = ({
+ formState,
+ setFormState,
+ isTaskAlertOpen,
+ closeTaskAlert,
+}) => {
+ const [, dispatch] = useContext(BulkEmailContext);
+
+ const {
+ isScheduled,
+ emailRecipients = { value: [] },
+ scheduleDate = '',
+ scheduleTime = '',
+ isEditMode = false,
+ subject,
+ courseId = '',
+ emailId = '',
+ schedulingId = '',
+ body,
+ isScheduleButtonClicked = false,
+ isFormSubmitted = false,
+ } = formState ?? {};
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const changeFormStatus = useCallback((status) => setFormState({ ...formState, formStatus: status }), []);
+
+ const handlePostEmailTask = async () => {
+ const emailRecipientsValue = emailRecipients.value;
+ const emailSubject = subject.value;
+ const emailBody = body.value;
+
+ const emailData = new FormData();
+ emailData.append('action', 'send');
+ emailData.append('send_to', JSON.stringify(emailRecipientsValue));
+ emailData.append('subject', emailSubject);
+ emailData.append('message', emailBody);
+
+ if (isScheduled) {
+ emailData.append('schedule', new Date(`${scheduleDate} ${scheduleTime}`).toISOString());
+ }
+
+ changeFormStatus('pending');
+
+ try {
+ await postBulkEmailInstructorTask(emailData, courseId);
+ const newFormStatus = isScheduled ? 'completeSchedule' : 'complete';
+ changeFormStatus(newFormStatus);
+ setTimeout(() => changeFormStatus('default'), 3000);
+ } catch {
+ changeFormStatus('error');
+ }
+ };
+
+ const handlePatchEmailTask = async () => {
+ const emailRecipientsValue = emailRecipients.value;
+ const emailSubject = subject.value;
+ const emailBody = body.value;
+
+ const emailData = {
+ email: {
+ targets: emailRecipientsValue,
+ subject: emailSubject,
+ message: emailBody,
+ id: emailId,
+ },
+ schedule: isScheduled ? new Date(`${scheduleDate} ${scheduleTime}`).toISOString() : null,
+ };
+
+ changeFormStatus('pending');
+
+ try {
+ await patchScheduledBulkEmailInstructorTask(emailData, courseId, schedulingId);
+ changeFormStatus('completeSchedule');
+ setTimeout(() => changeFormStatus('default'), 3000);
+ } catch {
+ changeFormStatus('error');
+ }
+ };
+
+ const createEmailTask = async () => {
+ const isScheduleValid = isScheduled ? scheduleDate.length > 0 && scheduleTime.length > 0 : true;
+ const isFormValid = emailRecipients.value.length > 0 && subject.value.length > 0
+ && body.value.length > 0 && isScheduleValid;
+
+ if (isFormValid && isEditMode) {
+ await handlePatchEmailTask();
+ }
+
+ if (isFormValid && !isEditMode) {
+ await handlePostEmailTask();
+ }
+
+ dispatch(getScheduledBulkEmailThunk(courseId, 1));
+ };
+
+ return (
+
+ )
+ : (
+
+ )}
+ close={(event) => {
+ closeTaskAlert();
+
+ if (event.target.name === 'continue') {
+ if (!isFormSubmitted) {
+ setFormState({ ...formState, isFormSubmitted: true });
+ }
+ if (isScheduleButtonClicked) {
+ setFormState({ ...formState, isScheduledSubmitted: true });
+ }
+
+ createEmailTask();
+ }
+ }}
+ />
+ );
+};
+
+TaskAlertModalForm.defaultProps = {
+ formState: {},
+ setFormState: () => {},
+ openTaskAlert: () => {},
+ closeTaskAlert: () => {},
+ isTaskAlertOpen: false,
+};
+
+TaskAlertModalForm.propTypes = {
+ formState: PropTypes.shape({}),
+ setFormState: PropTypes.func,
+ openTaskAlert: PropTypes.func,
+ closeTaskAlert: PropTypes.func,
+ isTaskAlertOpen: PropTypes.bool,
+
+};
+
+export default TaskAlertModalForm;
diff --git a/plugins/communications-app/TaskAlertModalForm/messages.alerts.js b/plugins/communications-app/TaskAlertModalForm/messages.alerts.js
new file mode 100644
index 00000000..c19cd647
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/messages.alerts.js
@@ -0,0 +1,41 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* AlertTypes.jsx Messages */
+ TaskAlertModalAlertTypesRecipients: {
+ id: 'task.alert.types.recipients',
+ defaultMessage: 'You are sending an email message with the subject {subject} to the following recipients:',
+ description: 'A warning shown to the user after submitting the email, to confirm the email recipients.',
+ },
+ TaskAlertModalAlertTypesInstructionCaption: {
+ id: 'task.alert.types.caution',
+ defaultMessage: 'Caution!',
+ description: 'Checkbox to schedule sending the email at a later date',
+ },
+ TaskAlertModalAlertTypesInstructionCaptionMessage: {
+ id: 'task.alert.types.caution.message',
+ defaultMessage:
+ ' When you select Send Email, you are creating a new email message that is added to the queue for sending, and cannot be cancelled.',
+ description: 'A warning about how emails are sent out to users',
+ },
+ TaskAlertModalAlertTypesEditingDate: {
+ id: 'task.alert.types.editing',
+ defaultMessage: 'You are editing a scheduled email to be sent on: {dateTime}',
+ description: 'This alert pops up before submitting when editing an email that has already been scheduled',
+ },
+ TaskAlertModalAlertTypesEditingSubject: {
+ id: 'task.alert.types.subject',
+ defaultMessage: 'with the subject: {subject}',
+ },
+ TaskAlertModalAlertTypesEditingTo: {
+ id: 'task.alert.types.to',
+ defaultMessage: 'to recipients:',
+ },
+ TaskAlertModalAlertTypesEditingWarning: {
+ id: 'task.alert.types.warning',
+ defaultMessage: 'This will not create a new scheduled email task and instead overwrite the one currently selected. Do you want to overwrite this scheduled email?',
+ description: 'This alert pops up before submitting when editing an email that has already been scheduled',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/TaskAlertModalForm/messages.js b/plugins/communications-app/TaskAlertModalForm/messages.js
new file mode 100644
index 00000000..c49310c3
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/messages.js
@@ -0,0 +1,49 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ /* index.jsx Messages */
+ ScheduleSectionScheduleBox: {
+ id: 'schedule.section.form.scheduleBox',
+ defaultMessage: 'Schedule this email for a future date',
+ description: 'Checkbox to schedule sending the email at a later date',
+ },
+ ScheduleSectionSubmitButtonDefault: {
+ id: 'schedule.section.submit.button.default',
+ defaultMessage: 'Send email',
+ },
+ ScheduleSectionSubmitFormError: {
+ id: 'schedule.section.submit.error',
+ defaultMessage: 'An error occured while attempting to send the email.',
+ description: 'An Error message located under the submit button for the email form. Visible only on a failure.',
+ },
+ ScheduleSectionSubmitFormSuccess: {
+ id: 'schedule.section.form.success',
+ defaultMessage: 'Email successfully created',
+ },
+ ScheduleSectionSubmitFormScheduledSuccess: {
+ id: 'schedule.section.submit.scheduled.success',
+ defaultMessage: 'Email successfully scheduled',
+ },
+ ScheduleSectionSubmitButtonSchedule: {
+ id: 'schedule.section.submit.button.schedule',
+ defaultMessage: 'Schedule Email',
+ },
+ ScheduleSectionSubmitButtonPending: {
+ id: 'schedule.section.submit.button.pending',
+ defaultMessage: 'Submitting',
+ },
+ ScheduleSectionSubmitButtonComplete: {
+ id: 'schedule.section.submit.button.send.complete',
+ defaultMessage: 'Email Created',
+ },
+ ScheduleSectionSubmitButtonError: {
+ id: 'schedule.section.submit.button.error',
+ defaultMessage: 'Error',
+ },
+ ScheduleSectionSubmitButtonCompleteSchedule: {
+ id: 'schedule.section.submit.button.schedule.complete',
+ defaultMessage: 'Scheduling Done',
+ },
+});
+
+export default messages;
diff --git a/plugins/communications-app/TaskAlertModalForm/package.json b/plugins/communications-app/TaskAlertModalForm/package.json
new file mode 100644
index 00000000..9640863e
--- /dev/null
+++ b/plugins/communications-app/TaskAlertModalForm/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@openedx-plugins/communications-app-task-alert-modal",
+ "version": "1.0.0",
+ "description": "openedx alert in build email form to use it in this mfe",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "peerDependencies": {
+ "@edx/frontend-app-communications": "*",
+ "@edx/frontend-platform": "*",
+ "@edx/paragon": "*",
+ "prop-types": "*",
+ "react": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edx/frontend-app-communications": {
+ "optional": true
+ }
+ }
+}
diff --git a/src/components/PluggableComponent/index.jsx b/src/components/PluggableComponent/index.jsx
index 5c19a9c5..96c0d198 100644
--- a/src/components/PluggableComponent/index.jsx
+++ b/src/components/PluggableComponent/index.jsx
@@ -1,4 +1,3 @@
-/* eslint-disable react-hooks/exhaustive-deps */
import React, { useState, useEffect, useRef } from 'react';
import loadable from '@loadable/component';
import PropTypes from 'prop-types';
@@ -11,6 +10,7 @@ import { isPluginAvailable } from './utils';
*
* @param {object} props - Component props
* @param {React.ReactNode} props.children - Child elements to be passed to the plugin component
+ * @param {React.ReactNode} props.loadingComponent - Component to be rendered while the plugin is loading
* @param {string} props.as - String indicating the module to import dynamically
* @param {string} props.id - Identifier for the plugin
* @param {object} props.pluggableComponentProps - Additional props to be passed to the component
@@ -18,15 +18,19 @@ import { isPluginAvailable } from './utils';
*/
const PluggableComponent = ({
children,
+ loadingComponent,
as,
id,
...pluggableComponentProps
}) => {
const [newComponent, setNewComponent] = useState(children || null);
const loadedComponentRef = useRef(null);
+ const [isLoadingComponent, setIsLoadingComponent] = useState(false);
useEffect(() => {
const loadPluginComponent = async () => {
+ setIsLoadingComponent(true);
+
try {
const hasModuleInstalled = await isPluginAvailable(as);
@@ -46,10 +50,13 @@ const PluggableComponent = ({
}
} catch (error) {
console.error(`Failed to load plugin ${as}:`, error);
+ } finally {
+ setIsLoadingComponent(false);
}
};
loadPluginComponent();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, as]);
useEffect(() => {
@@ -57,6 +64,7 @@ const PluggableComponent = ({
const updatedComponent = React.cloneElement(newComponent, pluggableComponentProps, children);
setNewComponent(updatedComponent);
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [children]);
useDeepCompareEffect(() => {
@@ -66,11 +74,16 @@ const PluggableComponent = ({
}
}, [pluggableComponentProps]);
- return newComponent;
+ return isLoadingComponent && loadingComponent ? loadingComponent : newComponent;
+};
+
+PluggableComponent.defaultProps = {
+ loadingComponent: null,
};
PluggableComponent.propTypes = {
children: PropTypes.node,
+ loadingComponent: PropTypes.node,
as: PropTypes.string,
id: PropTypes.string,
};
diff --git a/src/components/PluggableComponent/index.test.jsx b/src/components/PluggableComponent/index.test.jsx
index 6d28b19f..0ea37c81 100644
--- a/src/components/PluggableComponent/index.test.jsx
+++ b/src/components/PluggableComponent/index.test.jsx
@@ -92,7 +92,7 @@ describe('PluggableComponent', () => {
});
});
- test('updates component when props change', async () => {
+ it('updates component when props change', async () => {
const { rerender } = render(
{
expect(toggleContent).toHaveTextContent('Toggle On');
});
});
+
+ it('renders loadingComponent while the plugin is loading', async () => {
+ jest.mock('./utils', () => ({
+ isPluginAvailable: jest.fn().mockImplementation(
+ () => new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(true);
+ }, 1000);
+ }),
+ ),
+ }));
+
+ await waitFor(() => {
+ const { getByText } = render(
+ Loading...}
+ />,
+ );
+ expect(getByText('Loading...')).toBeInTheDocument();
+ });
+ });
});
diff --git a/src/components/PluggableComponent/utils.js b/src/components/PluggableComponent/utils.js
index 1fb7e255..55f86fce 100644
--- a/src/components/PluggableComponent/utils.js
+++ b/src/components/PluggableComponent/utils.js
@@ -4,7 +4,7 @@ export const isPluginAvailable = async (pluginName) => {
try {
await import(`@node_modules/@openedx-plugins/${pluginName}`);
return true;
- } catch (error) {
+ } catch {
return false;
}
};
diff --git a/src/components/bulk-email-tool/BulkEmailTool.jsx b/src/components/bulk-email-tool/BulkEmailTool.jsx
index 79cac2b6..096334d7 100644
--- a/src/components/bulk-email-tool/BulkEmailTool.jsx
+++ b/src/components/bulk-email-tool/BulkEmailTool.jsx
@@ -7,7 +7,7 @@ import { Container } from '@edx/paragon';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import BulkEmailTaskManager from './bulk-email-task-manager/BulkEmailTaskManager';
import NavigationTabs from '../navigation-tabs/NavigationTabs';
-import BulkEmailForm from './bulk-email-form';
+import BuildEmailFormExtensible from './bulk-email-form/BuildEmailFormExtensible';
import { CourseMetadataContext } from '../page-container/PageContainer';
import { BulkEmailProvider } from './bulk-email-context';
import BackToInstructor from '../navigation-tabs/BackToInstructor';
@@ -33,7 +33,7 @@ export default function BulkEmailTool() {
-
+
diff --git a/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible.jsx b/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible.jsx
new file mode 100644
index 00000000..264f8ccb
--- /dev/null
+++ b/src/components/bulk-email-tool/bulk-email-form/BuildEmailFormExtensible.jsx
@@ -0,0 +1,135 @@
+import { useState, useEffect, useContext } from 'react';
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import {
+ Form,
+ Spinner,
+ useToggle,
+} from '@edx/paragon';
+import { BulkEmailContext } from '../bulk-email-context';
+import useMobileResponsive from '../../../utils/useMobileResponsive';
+import PluggableComponent from '../../PluggableComponent';
+
+function BuildEmailFormExtensible({ courseId, cohorts }) {
+ const isMobile = useMobileResponsive();
+ const [{ editor }] = useContext(BulkEmailContext);
+ const [isTaskAlertOpen, openTaskAlert, closeTaskAlert] = useToggle(false);
+ const [formState, setFormState] = useState({
+ isFormValid: true,
+ isFormSubmitted: false,
+ scheduleValid: true,
+ isScheduled: false,
+ isEditMode: false,
+ formStatus: 'default',
+ isScheduleButtonClicked: false,
+ courseId,
+ cohorts,
+ scheduleDate: '',
+ scheduleTime: '',
+ isScheduledSubmitted: false,
+ emailId: '',
+ schedulingId: '',
+ emailRecipients: { value: [], isLoaded: false },
+ subject: { value: '', isLoaded: false },
+ body: { value: '', isLoaded: false },
+ });
+
+ useEffect(() => {
+ if (editor.editMode) {
+ const { emailRecipients, subject, body } = formState;
+ const newRecipientsValue = { ...emailRecipients, value: editor.emailRecipients };
+ const newSubjectValue = { ...subject, value: editor.emailSubject };
+ const newBodyValue = { ...body, value: editor.emailBody };
+ const newScheduleDate = editor.scheduleDate;
+ const newScheduleTime = editor.scheduleTime;
+ const newEmailId = editor.emailId;
+ const newSchedulingId = editor.schedulingId;
+
+ setFormState({
+ ...formState,
+ isEditMode: true,
+ formStatus: 'reschedule',
+ isScheduled: true,
+ emailId: newEmailId,
+ schedulingId: newSchedulingId,
+ scheduleDate: newScheduleDate,
+ scheduleTime: newScheduleTime,
+ emailRecipients: newRecipientsValue,
+ subject: newSubjectValue,
+ body: newBodyValue,
+ });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [editor.editMode, editor.emailRecipients, editor.emailSubject, editor.emailBody]);
+
+ return (
+
+ );
+}
+
+BuildEmailFormExtensible.defaultProps = {
+ cohorts: [],
+};
+
+BuildEmailFormExtensible.propTypes = {
+ courseId: PropTypes.string.isRequired,
+ cohorts: PropTypes.arrayOf(PropTypes.string),
+};
+
+export default BuildEmailFormExtensible;
diff --git a/webpack.dev.config.js b/webpack.dev.config.js
index 61dd9d90..660d88df 100644
--- a/webpack.dev.config.js
+++ b/webpack.dev.config.js
@@ -29,6 +29,7 @@ config.module.rules = [webpack5esmInteropRule, ...otherRules];
const alias = {
'@node_modules': path.resolve(__dirname, 'node_modules'),
+ '@communications-app': path.resolve(__dirname, '.'),
};
config.resolve.alias = { ...config.resolve.alias, ...alias };
diff --git a/webpack.prod.config.js b/webpack.prod.config.js
index 0e5b226c..9e0aba2e 100644
--- a/webpack.prod.config.js
+++ b/webpack.prod.config.js
@@ -21,6 +21,7 @@ config.module.rules = [webpack5esmInteropRule, ...otherRules];
const alias = {
'@node_modules': path.resolve(__dirname, 'node_modules'),
+ '@communications-app': path.resolve(__dirname, '.'),
};
config.resolve.alias = { ...config.resolve.alias, ...alias };