diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4574a6d186..1a60a0d1c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,14 +4,33 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
+#### [v6.166.0](https://github.com/opengovsg/FormSG/compare/v6.165.0...v6.166.0)
+
+- feat(mrf): conditional routing [`#7804`](https://github.com/opengovsg/FormSG/pull/7804)
+- fix: trim text input before sending to backend [`#7937`](https://github.com/opengovsg/FormSG/pull/7937)
+- test(story): add chromatic story to ensure thankyou page renders [`#7264`](https://github.com/opengovsg/FormSG/pull/7264)
+- chore: remove unused deps [`#7906`](https://github.com/opengovsg/FormSG/pull/7906)
+- feat(i18n): extract text from admin-form prompts [`#7931`](https://github.com/opengovsg/FormSG/pull/7931)
+- fix(deps): bump libphonenumber-js from 1.11.14 to 1.11.15 in /shared [`#7936`](https://github.com/opengovsg/FormSG/pull/7936)
+- chore(deps-dev): bump @playwright/test from 1.45.1 to 1.49.0 [`#7923`](https://github.com/opengovsg/FormSG/pull/7923)
+- chore(FE): cleanup date check on sex field [`#7922`](https://github.com/opengovsg/FormSG/pull/7922)
+- chore(FE): use consistent vite port for dev with README [`#7930`](https://github.com/opengovsg/FormSG/pull/7930)
+- feat(i18n): move workspace relative date format [`#7929`](https://github.com/opengovsg/FormSG/pull/7929)
+- build: release v6.165.0 [`#7928`](https://github.com/opengovsg/FormSG/pull/7928)
+- build: release v6.165.0 [`#7927`](https://github.com/opengovsg/FormSG/pull/7927)
+- feat(i18n): extract text for form metadata [`#7926`](https://github.com/opengovsg/FormSG/pull/7926)
+
#### [v6.165.0](https://github.com/opengovsg/FormSG/compare/v6.164.0...v6.165.0)
+> 20 November 2024
+
- chore(approvals-ga): remove beta flag for MRF email notifications and approvals [`#7920`](https://github.com/opengovsg/FormSG/pull/7920)
- chore(deps-dev): bump @typescript-eslint/parser from 8.14.0 to 8.15.0 in /shared [`#7911`](https://github.com/opengovsg/FormSG/pull/7911)
- fix(deps): bump @aws-sdk/client-lambda from 3.654.0 to 3.693.0 [`#7910`](https://github.com/opengovsg/FormSG/pull/7910)
- chore(deps-dev): bump @typescript-eslint/eslint-plugin from 8.14.0 to 8.15.0 in /shared [`#7912`](https://github.com/opengovsg/FormSG/pull/7912)
- build: merge release v6.164.0 to develop [`#7921`](https://github.com/opengovsg/FormSG/pull/7921)
- build: release v6.164.0 [`#7919`](https://github.com/opengovsg/FormSG/pull/7919)
+- chore: bump version to v6.165.0 [`7984ee9`](https://github.com/opengovsg/FormSG/commit/7984ee906d7c6eef156a15569694f9c6784f9e8a)
#### [v6.164.0](https://github.com/opengovsg/FormSG/compare/v6.163.0...v6.164.0)
diff --git a/README.md b/README.md
index 7cb8ac3556..9d5866e476 100755
--- a/README.md
+++ b/README.md
@@ -103,7 +103,7 @@ npm run dev
After the Docker image has finished building, the following local applications can be accessed:
-- React application can be accessed at [localhost:3000](localhost:3000)
+- React application can be accessed at [localhost:5173](localhost:5173)
- The backend API server can be accessed at [localhost:5001](localhost:5001)
- The development mail server can be accessed at [localhost:1080](localhost:1080)
diff --git a/__tests__/integration/helpers/express-setup.ts b/__tests__/integration/helpers/express-setup.ts
index 6b2afebe97..35c598857a 100644
--- a/__tests__/integration/helpers/express-setup.ts
+++ b/__tests__/integration/helpers/express-setup.ts
@@ -2,7 +2,6 @@ import compression from 'compression'
import cookieParser from 'cookie-parser'
import express, { Express, Router } from 'express'
import session from 'express-session'
-import nocache from 'nocache'
import { errorHandlerMiddlewares } from 'src/app/loaders/express/error-handler'
import helmetMiddlewares from 'src/app/loaders/express/helmet'
@@ -37,7 +36,6 @@ export const setupApp = (
app.use(compression())
app.use(parserMiddlewares())
app.use(helmetMiddlewares())
- app.use(nocache())
app.use(testSessionMiddlewares())
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md
index 4821449c6a..fb0edafb18 100644
--- a/docs/TROUBLESHOOTING.md
+++ b/docs/TROUBLESHOOTING.md
@@ -41,19 +41,6 @@ conf.members[0].host = 'database:27017'
rs.reconfig(conf, {force:true})
```
-## `TypeError: Cannot read property 'tap' of undefined`
-
-If you are using a machine with an M1 chip, you may be facing this issue due to your `node` installation, as an inappropriate version of `node` would cause more updated versions of `webpack` to be installed and thus introduces breaking changes in the code.
-
-To overcome this, use Rosetta to install `node` in -x86_64 architecture as described in the "Macs with M1 chip" section [here](https://github.com/nvm-sh/nvm#macos-troubleshooting).
-
-Be sure to check that the correct `node` is installed with:
-
-```bash
-$ node -p process.arch
-x64
-```
-
## `npm` not found
Make sure `nvm` is loaded to the environment in `~/.bash_profile`. To do so, the `~/.bash_profile` should include the following lines:
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 4a7ef43251..9133872b85 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "form-frontend",
- "version": "6.165.0",
+ "version": "6.166.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "form-frontend",
- "version": "6.165.0",
+ "version": "6.166.0",
"hasInstallScript": true,
"dependencies": {
"@chakra-ui/react": "^2.8.2",
@@ -90,7 +90,6 @@
"tweetnacl": "^1.0.3",
"type-fest": "^4.17.0",
"typescript": "^5.4.5",
- "use-debounce": "^7.0.1",
"use-draggable-scroll": "^0.1.0",
"validator": "^13.7.0",
"vite-plugin-node-stdlib-browser": "^0.2.1",
@@ -149,7 +148,6 @@
"msw": "^1.3.3",
"msw-storybook-addon": "^1.10.0",
"prettier": "^3.2.5",
- "puppeteer": "^13.0.0",
"react-refresh": "^0.14.0",
"resize-observer-polyfill": "^1.5.1",
"rimraf": "^3.0.2",
@@ -158,8 +156,7 @@
"vite": "^5.4.6",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.2",
- "vitest": "^1.5.2",
- "worker-loader": "^3.0.8"
+ "vitest": "^1.5.2"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -347,6 +344,7 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
"integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "dev": true,
"engines": {
"node": ">=6.9.0"
}
@@ -427,20 +425,6 @@
"node": ">=6.0.0"
}
},
- "node_modules/@babel/plugin-syntax-jsx": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
- "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
@@ -2174,27 +2158,28 @@
}
},
"node_modules/@emotion/babel-plugin": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz",
- "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==",
- "dependencies": {
- "@babel/helper-module-imports": "^7.12.13",
- "@babel/plugin-syntax-jsx": "^7.12.13",
- "@babel/runtime": "^7.13.10",
- "@emotion/hash": "^0.8.0",
- "@emotion/memoize": "^0.7.5",
- "@emotion/serialize": "^1.0.2",
- "babel-plugin-macros": "^2.6.1",
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
+ "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.2.0",
+ "babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
- "stylis": "^4.0.3"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0"
+ "stylis": "4.2.0"
}
},
+ "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
"node_modules/@emotion/cache": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.6.0.tgz",
@@ -2208,18 +2193,23 @@
}
},
"node_modules/@emotion/hash": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
- "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
},
"node_modules/@emotion/is-prop-valid": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
- "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+ "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
"dependencies": {
- "@emotion/memoize": "^0.7.4"
+ "@emotion/memoize": "^0.9.0"
}
},
+ "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
"node_modules/@emotion/memoize": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
@@ -2252,56 +2242,66 @@
}
},
"node_modules/@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+ "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
"dependencies": {
- "@emotion/hash": "^0.8.0",
- "@emotion/memoize": "^0.7.4",
- "@emotion/unitless": "^0.7.5",
- "@emotion/utils": "^1.0.0",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.1",
"csstype": "^3.0.2"
}
},
+ "node_modules/@emotion/serialize/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
"node_modules/@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
},
"node_modules/@emotion/styled": {
- "version": "11.6.0",
- "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz",
- "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==",
+ "version": "11.13.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
+ "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
"dependencies": {
- "@babel/runtime": "^7.13.10",
- "@emotion/babel-plugin": "^11.3.0",
- "@emotion/is-prop-valid": "^1.1.1",
- "@emotion/serialize": "^1.0.2",
- "@emotion/utils": "^1.0.0"
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.12.0",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.0",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+ "@emotion/utils": "^1.4.0"
},
"peerDependencies": {
- "@babel/core": "^7.0.0",
"@emotion/react": "^11.0.0-rc.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
- "@babel/core": {
- "optional": true
- },
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/unitless": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
- "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+ "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
},
"node_modules/@emotion/utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
- "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+ "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.2.5",
@@ -5525,16 +5525,6 @@
"integrity": "sha512-+jBxVvXVuggZOrm04NR8z+5+bgoW4VZyLzUO+hmPPW1mVFL/HaitLAkizfv4yg9TbG8lkfHWVMQ11yDqrVVCzA==",
"dev": true
},
- "node_modules/@types/yauzl": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
- "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
- "dev": true,
- "optional": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz",
@@ -6546,18 +6536,6 @@
"node": ">= 10.0.0"
}
},
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -6946,13 +6924,32 @@
}
},
"node_modules/babel-plugin-macros": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
- "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"dependencies": {
- "@babel/runtime": "^7.7.2",
- "cosmiconfig": "^6.0.0",
- "resolve": "^1.12.0"
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/babel-plugin-macros/node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/bail": {
@@ -7331,15 +7328,6 @@
"ieee754": "^1.1.13"
}
},
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
"node_modules/buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
@@ -8117,12 +8105,9 @@
}
},
"node_modules/convert-source-map": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
- "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
- "dependencies": {
- "safe-buffer": "~5.1.1"
- }
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"node_modules/cookie": {
"version": "0.6.0",
@@ -8805,12 +8790,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/devtools-protocol": {
- "version": "0.0.937139",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.937139.tgz",
- "integrity": "sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ==",
- "dev": true
- },
"node_modules/diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -8967,15 +8946,6 @@
"node": ">= 0.8"
}
},
- "node_modules/end-of-stream": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
- "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "dev": true,
- "dependencies": {
- "once": "^1.4.0"
- }
- },
"node_modules/endent": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz",
@@ -10299,41 +10269,6 @@
"node": ">=4"
}
},
- "node_modules/extract-zip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
- "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
- "dev": true,
- "dependencies": {
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "bin": {
- "extract-zip": "cli.js"
- },
- "engines": {
- "node": ">= 10.17.0"
- },
- "optionalDependencies": {
- "@types/yauzl": "^2.9.1"
- }
- },
- "node_modules/extract-zip/node_modules/get-stream": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
- "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
- "dev": true,
- "dependencies": {
- "pump": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -10395,15 +10330,6 @@
"reusify": "^1.0.4"
}
},
- "node_modules/fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
- "dev": true,
- "dependencies": {
- "pend": "~1.2.0"
- }
- },
"node_modules/figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -10766,12 +10692,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "dev": true
- },
"node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -11403,19 +11323,6 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="
},
- "node_modules/https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "dev": true,
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -13915,12 +13822,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "dev": true
- },
"node_modules/mlly": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
@@ -14996,12 +14897,6 @@
"node": ">=0.12"
}
},
- "node_modules/pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
- "dev": true
- },
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -15243,15 +15138,6 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
- "node_modules/progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -15307,16 +15193,6 @@
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
- "node_modules/pump": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
- "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
- "dev": true,
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -15325,102 +15201,6 @@
"node": ">=6"
}
},
- "node_modules/puppeteer": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.0.0.tgz",
- "integrity": "sha512-kZfGAieIVSo4bFqYuvY2KvhgP9txzmPbbnpZIzLlfdt8nEu9evXEwsbBt1BHocVQM4fJmCiS+FRyw7c8aWadNg==",
- "dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "debug": "4.3.2",
- "devtools-protocol": "0.0.937139",
- "extract-zip": "2.0.1",
- "https-proxy-agent": "5.0.0",
- "node-fetch": "2.6.5",
- "pkg-dir": "4.2.0",
- "progress": "2.0.3",
- "proxy-from-env": "1.1.0",
- "rimraf": "3.0.2",
- "tar-fs": "2.1.1",
- "unbzip2-stream": "1.4.3",
- "ws": "8.2.3"
- },
- "engines": {
- "node": ">=10.18.1"
- }
- },
- "node_modules/puppeteer/node_modules/debug": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/puppeteer/node_modules/node-fetch": {
- "version": "2.6.5",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
- "dev": true,
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- }
- },
- "node_modules/puppeteer/node_modules/tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
- "dev": true
- },
- "node_modules/puppeteer/node_modules/webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
- "dev": true
- },
- "node_modules/puppeteer/node_modules/whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "dev": true,
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/puppeteer/node_modules/ws": {
- "version": "8.2.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
- "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
- "dev": true,
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -17260,7 +17040,7 @@
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"engines": {
"node": ">=0.10.0"
}
@@ -17628,9 +17408,9 @@
}
},
"node_modules/stylis": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.11.tgz",
- "integrity": "sha512-Q7gWFqMXrpDAnqhkFZdgBjXzybYc4WaJNtFdnQQSfww/cIiBUx/3sPGYMKFh3muXA47dhaWfYqc/HQkg8j7+ig=="
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
"node_modules/supports-color": {
"version": "5.5.0",
@@ -17689,40 +17469,6 @@
"node": ">=6"
}
},
- "node_modules/tar-fs": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
- "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
- "dev": true,
- "dependencies": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- }
- },
- "node_modules/tar-fs/node_modules/chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "dev": true
- },
- "node_modules/tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "dev": true,
- "dependencies": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/telejson": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz",
@@ -18166,16 +17912,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/unbzip2-stream": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
- "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
- "dev": true,
- "dependencies": {
- "buffer": "^5.2.1",
- "through": "^2.3.8"
- }
- },
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
@@ -18416,17 +18152,6 @@
"react": "^16.8.0 || ^17.0.0"
}
},
- "node_modules/use-debounce": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-7.0.1.tgz",
- "integrity": "sha512-fOrzIw2wstbAJuv8PC9Vg4XgwyTLEOdq4y/Z3IhVl8DAE4svRcgyEUvrEXu+BMNgMoc3YND6qLT61kkgEKXh7Q==",
- "engines": {
- "node": ">= 10.0.0"
- },
- "peerDependencies": {
- "react": ">=16.8.0"
- }
- },
"node_modules/use-draggable-scroll": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/use-draggable-scroll/-/use-draggable-scroll-0.1.0.tgz",
@@ -19391,44 +19116,6 @@
"node": ">=8"
}
},
- "node_modules/worker-loader": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
- "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
- "dev": true,
- "dependencies": {
- "loader-utils": "^2.0.0",
- "schema-utils": "^3.0.0"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "^4.0.0 || ^5.0.0"
- }
- },
- "node_modules/worker-loader/node_modules/schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "dependencies": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- },
- "engines": {
- "node": ">= 10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- }
- },
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -19554,16 +19241,6 @@
"node": ">=12"
}
},
- "node_modules/yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
- "dev": true,
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -19756,7 +19433,8 @@
"@babel/helper-plugin-utils": {
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz",
- "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg=="
+ "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==",
+ "dev": true
},
"@babel/helper-simple-access": {
"version": "7.24.7",
@@ -19810,14 +19488,6 @@
"@babel/types": "^7.25.6"
}
},
- "@babel/plugin-syntax-jsx": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz",
- "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==",
- "requires": {
- "@babel/helper-plugin-utils": "^7.24.0"
- }
- },
"@babel/plugin-transform-react-jsx-self": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz",
@@ -21007,22 +20677,28 @@
}
},
"@emotion/babel-plugin": {
- "version": "11.3.0",
- "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.3.0.tgz",
- "integrity": "sha512-UZKwBV2rADuhRp+ZOGgNWg2eYgbzKzQXfQPtJbu/PLy8onurxlNCLvxMQEvlr1/GudguPI5IU9qIY1+2z1M5bA==",
- "requires": {
- "@babel/helper-module-imports": "^7.12.13",
- "@babel/plugin-syntax-jsx": "^7.12.13",
- "@babel/runtime": "^7.13.10",
- "@emotion/hash": "^0.8.0",
- "@emotion/memoize": "^0.7.5",
- "@emotion/serialize": "^1.0.2",
- "babel-plugin-macros": "^2.6.1",
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz",
+ "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==",
+ "requires": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.2.0",
+ "babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
- "stylis": "^4.0.3"
+ "stylis": "4.2.0"
+ },
+ "dependencies": {
+ "@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ }
}
},
"@emotion/cache": {
@@ -21038,16 +20714,23 @@
}
},
"@emotion/hash": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
- "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
},
"@emotion/is-prop-valid": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.1.tgz",
- "integrity": "sha512-bW1Tos67CZkOURLc0OalnfxtSXQJMrAMV0jZTVGJUPSOd4qgjF3+tTD5CwJM13PHA8cltGW1WGbbvV9NpvUZPw==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+ "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
"requires": {
- "@emotion/memoize": "^0.7.4"
+ "@emotion/memoize": "^0.9.0"
+ },
+ "dependencies": {
+ "@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ }
}
},
"@emotion/memoize": {
@@ -21070,15 +20753,22 @@
}
},
"@emotion/serialize": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.2.tgz",
- "integrity": "sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz",
+ "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==",
"requires": {
- "@emotion/hash": "^0.8.0",
- "@emotion/memoize": "^0.7.4",
- "@emotion/unitless": "^0.7.5",
- "@emotion/utils": "^1.0.0",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.1",
"csstype": "^3.0.2"
+ },
+ "dependencies": {
+ "@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ }
}
},
"@emotion/sheet": {
@@ -21087,26 +20777,32 @@
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
},
"@emotion/styled": {
- "version": "11.6.0",
- "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.6.0.tgz",
- "integrity": "sha512-mxVtVyIOTmCAkFbwIp+nCjTXJNgcz4VWkOYQro87jE2QBTydnkiYusMrRGFtzuruiGK4dDaNORk4gH049iiQuw==",
+ "version": "11.13.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz",
+ "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==",
"requires": {
- "@babel/runtime": "^7.13.10",
- "@emotion/babel-plugin": "^11.3.0",
- "@emotion/is-prop-valid": "^1.1.1",
- "@emotion/serialize": "^1.0.2",
- "@emotion/utils": "^1.0.0"
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.12.0",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.0",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0",
+ "@emotion/utils": "^1.4.0"
}
},
"@emotion/unitless": {
- "version": "0.7.5",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
- "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
+ },
+ "@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz",
+ "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw=="
},
"@emotion/utils": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.0.0.tgz",
- "integrity": "sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA=="
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz",
+ "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA=="
},
"@emotion/weak-memoize": {
"version": "0.2.5",
@@ -23259,16 +22955,6 @@
"integrity": "sha512-+jBxVvXVuggZOrm04NR8z+5+bgoW4VZyLzUO+hmPPW1mVFL/HaitLAkizfv4yg9TbG8lkfHWVMQ11yDqrVVCzA==",
"dev": true
},
- "@types/yauzl": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
- "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==",
- "dev": true,
- "optional": true,
- "requires": {
- "@types/node": "*"
- }
- },
"@typescript-eslint/eslint-plugin": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.14.0.tgz",
@@ -23953,15 +23639,6 @@
"resolved": "https://registry.npmjs.org/address/-/address-1.2.1.tgz",
"integrity": "sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA=="
},
- "agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "requires": {
- "debug": "4"
- }
- },
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -24244,13 +23921,27 @@
}
},
"babel-plugin-macros": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
- "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"requires": {
- "@babel/runtime": "^7.7.2",
- "cosmiconfig": "^6.0.0",
- "resolve": "^1.12.0"
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "dependencies": {
+ "cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "requires": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ }
+ }
}
},
"bail": {
@@ -24545,12 +24236,6 @@
"ieee754": "^1.1.13"
}
},
- "buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
- "dev": true
- },
"buffer-xor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
@@ -25115,12 +24800,9 @@
"dev": true
},
"convert-source-map": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
- "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
- "requires": {
- "safe-buffer": "~5.1.1"
- }
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
},
"cookie": {
"version": "0.6.0",
@@ -25678,12 +25360,6 @@
"dequal": "^2.0.0"
}
},
- "devtools-protocol": {
- "version": "0.0.937139",
- "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.937139.tgz",
- "integrity": "sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ==",
- "dev": true
- },
"diffie-hellman": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@@ -25822,15 +25498,6 @@
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"dev": true
},
- "end-of-stream": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
- "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
- "dev": true,
- "requires": {
- "once": "^1.4.0"
- }
- },
"endent": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/endent/-/endent-2.1.0.tgz",
@@ -26794,29 +26461,6 @@
"tmp": "^0.0.33"
}
},
- "extract-zip": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
- "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
- "dev": true,
- "requires": {
- "@types/yauzl": "^2.9.1",
- "debug": "^4.1.1",
- "get-stream": "^5.1.0",
- "yauzl": "^2.10.0"
- },
- "dependencies": {
- "get-stream": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
- "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
- "dev": true,
- "requires": {
- "pump": "^3.0.0"
- }
- }
- }
- },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -26875,15 +26519,6 @@
"reusify": "^1.0.4"
}
},
- "fd-slicer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
- "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
- "dev": true,
- "requires": {
- "pend": "~1.2.0"
- }
- },
"figures": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
@@ -27136,12 +26771,6 @@
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"dev": true
},
- "fs-constants": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
- "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
- "dev": true
- },
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -27596,16 +27225,6 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg=="
},
- "https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "dev": true,
- "requires": {
- "agent-base": "6",
- "debug": "4"
- }
- },
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -29321,12 +28940,6 @@
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
"dev": true
},
- "mkdirp-classic": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
- "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
- "dev": true
- },
"mlly": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
@@ -30104,12 +29717,6 @@
"sha.js": "^2.4.8"
}
},
- "pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
- "dev": true
- },
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -30276,12 +29883,6 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
- "progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "dev": true
- },
"prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -30332,89 +29933,11 @@
}
}
},
- "pump": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
- "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
- "dev": true,
- "requires": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
- "puppeteer": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.0.0.tgz",
- "integrity": "sha512-kZfGAieIVSo4bFqYuvY2KvhgP9txzmPbbnpZIzLlfdt8nEu9evXEwsbBt1BHocVQM4fJmCiS+FRyw7c8aWadNg==",
- "dev": true,
- "requires": {
- "debug": "4.3.2",
- "devtools-protocol": "0.0.937139",
- "extract-zip": "2.0.1",
- "https-proxy-agent": "5.0.0",
- "node-fetch": "2.6.5",
- "pkg-dir": "4.2.0",
- "progress": "2.0.3",
- "proxy-from-env": "1.1.0",
- "rimraf": "3.0.2",
- "tar-fs": "2.1.1",
- "unbzip2-stream": "1.4.3",
- "ws": "8.2.3"
- },
- "dependencies": {
- "debug": {
- "version": "4.3.2",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
- "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- }
- },
- "node-fetch": {
- "version": "2.6.5",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz",
- "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==",
- "dev": true,
- "requires": {
- "whatwg-url": "^5.0.0"
- }
- },
- "tr46": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
- "dev": true
- },
- "webidl-conversions": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
- "dev": true
- },
- "whatwg-url": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
- "dev": true,
- "requires": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "ws": {
- "version": "8.2.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
- "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
- "dev": true
- }
- }
- },
"qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
@@ -31756,7 +31279,7 @@
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="
},
"source-map-js": {
"version": "1.2.1",
@@ -32036,9 +31559,9 @@
}
},
"stylis": {
- "version": "4.0.11",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.11.tgz",
- "integrity": "sha512-Q7gWFqMXrpDAnqhkFZdgBjXzybYc4WaJNtFdnQQSfww/cIiBUx/3sPGYMKFh3muXA47dhaWfYqc/HQkg8j7+ig=="
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
},
"supports-color": {
"version": "5.5.0",
@@ -32079,39 +31602,6 @@
"resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz",
"integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA=="
},
- "tar-fs": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
- "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
- "dev": true,
- "requires": {
- "chownr": "^1.1.1",
- "mkdirp-classic": "^0.5.2",
- "pump": "^3.0.0",
- "tar-stream": "^2.1.4"
- },
- "dependencies": {
- "chownr": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
- "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
- "dev": true
- }
- }
- },
- "tar-stream": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
- "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
- "dev": true,
- "requires": {
- "bl": "^4.0.3",
- "end-of-stream": "^1.4.1",
- "fs-constants": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1"
- }
- },
"telejson": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz",
@@ -32438,16 +31928,6 @@
"which-boxed-primitive": "^1.0.2"
}
},
- "unbzip2-stream": {
- "version": "1.4.3",
- "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
- "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
- "dev": true,
- "requires": {
- "buffer": "^5.2.1",
- "through": "^2.3.8"
- }
- },
"undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
@@ -32606,11 +32086,6 @@
"ts-essentials": "^2.0.3"
}
},
- "use-debounce": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-7.0.1.tgz",
- "integrity": "sha512-fOrzIw2wstbAJuv8PC9Vg4XgwyTLEOdq4y/Z3IhVl8DAE4svRcgyEUvrEXu+BMNgMoc3YND6qLT61kkgEKXh7Q=="
- },
"use-draggable-scroll": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/use-draggable-scroll/-/use-draggable-scroll-0.1.0.tgz",
@@ -33149,29 +32624,6 @@
"stackback": "0.0.2"
}
},
- "worker-loader": {
- "version": "3.0.8",
- "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
- "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
- "dev": true,
- "requires": {
- "loader-utils": "^2.0.0",
- "schema-utils": "^3.0.0"
- },
- "dependencies": {
- "schema-utils": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
- "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
- "dev": true,
- "requires": {
- "@types/json-schema": "^7.0.8",
- "ajv": "^6.12.5",
- "ajv-keywords": "^3.5.2"
- }
- }
- }
- },
"wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
@@ -33257,16 +32709,6 @@
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
- "yauzl": {
- "version": "2.10.0",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
- "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
- "dev": true,
- "requires": {
- "buffer-crc32": "~0.2.3",
- "fd-slicer": "~1.1.0"
- }
- },
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 0581bc899b..5c4a8720a1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "form-frontend",
- "version": "6.165.0",
+ "version": "6.166.0",
"homepage": ".",
"type": "module",
"private": true,
@@ -86,7 +86,6 @@
"tweetnacl": "^1.0.3",
"type-fest": "^4.17.0",
"typescript": "^5.4.5",
- "use-debounce": "^7.0.1",
"use-draggable-scroll": "^0.1.0",
"validator": "^13.7.0",
"vite-plugin-node-stdlib-browser": "^0.2.1",
@@ -172,7 +171,6 @@
"msw": "^1.3.3",
"msw-storybook-addon": "^1.10.0",
"prettier": "^3.2.5",
- "puppeteer": "^13.0.0",
"react-refresh": "^0.14.0",
"resize-observer-polyfill": "^1.5.1",
"rimraf": "^3.0.2",
@@ -181,8 +179,7 @@
"vite": "^5.4.6",
"vite-plugin-svgr": "^4.2.0",
"vite-tsconfig-paths": "^4.3.2",
- "vitest": "^1.5.2",
- "worker-loader": "^3.0.8"
+ "vitest": "^1.5.2"
},
"proxy": "http://localhost:5001",
"msw": {
diff --git a/frontend/src/components/Button/NextAndBackButtonGroup.tsx b/frontend/src/components/Button/NextAndBackButtonGroup.tsx
new file mode 100644
index 0000000000..b97cfb796a
--- /dev/null
+++ b/frontend/src/components/Button/NextAndBackButtonGroup.tsx
@@ -0,0 +1,55 @@
+import { Stack } from '@chakra-ui/react'
+
+import { useIsMobile } from '~hooks/useIsMobile'
+
+import { Button } from './Button'
+
+interface NextAndBackButtonProps {
+ handleBack: () => void
+ handleNext: () => void
+ nextButtonLabel?: string
+ backButtonLabel?: string
+ isNextDisabled?: boolean
+ isBackDisabled?: boolean
+ nextButtonColorScheme?: 'danger'
+}
+
+export const NextAndBackButtonGroup = ({
+ handleBack,
+ handleNext,
+ nextButtonLabel = 'Next',
+ backButtonLabel = 'Back',
+ isNextDisabled = false,
+ isBackDisabled = false,
+ nextButtonColorScheme,
+}: NextAndBackButtonProps): JSX.Element => {
+ const isMobile = useIsMobile()
+
+ return (
+
+
+
+
+ )
+}
diff --git a/frontend/src/components/Button/index.ts b/frontend/src/components/Button/index.ts
index 488da99022..1391d60646 100644
--- a/frontend/src/components/Button/index.ts
+++ b/frontend/src/components/Button/index.ts
@@ -1,2 +1,3 @@
export type { ButtonProps } from './Button'
export { Button as default } from './Button'
+export * from './NextAndBackButtonGroup'
diff --git a/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx b/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx
index fced365825..5305476ea5 100644
--- a/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx
+++ b/frontend/src/features/admin-form/common/components/DirtyModal/DirtyModal.tsx
@@ -1,3 +1,5 @@
+import { useTranslation } from 'react-i18next'
+
import { UnsavedChangesModal } from '~templates/NavigationPrompt'
import { usePaymentStore } from '~features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/FieldListDrawer/field-panels/usePaymentStore'
@@ -93,12 +95,13 @@ export const useDirtyModal = () => {
export const DirtyModal = (): JSX.Element => {
const { isOpen, handleCancelNavigate, handleConfirmNavigate } =
useDirtyModal()
+ const { t } = useTranslation()
return (
diff --git a/frontend/src/features/admin-form/common/constants.ts b/frontend/src/features/admin-form/common/constants.ts
deleted file mode 100644
index 50b574a1fe..0000000000
--- a/frontend/src/features/admin-form/common/constants.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { FormResponseMode } from '~shared/types'
-
-export const RESPONSE_MODE_TO_TEXT: { [key in FormResponseMode]: string } = {
- [FormResponseMode.Multirespondent]: 'Multi-respondent form',
- [FormResponseMode.Email]: 'Email mode',
- [FormResponseMode.Encrypt]: 'Storage mode',
-}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
index 1d3f96b566..55636375cc 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx
@@ -408,7 +408,7 @@ const FieldButtonGroup = ({
}
onClick={handleEditFieldClick}
/>
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/PaymentView.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/PaymentView.tsx
index fab0fa421a..7d7a4f6f7a 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/PaymentView.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/PaymentView.tsx
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
import { BiCog, BiTrash } from 'react-icons/bi'
import { Box, ButtonGroup, Collapse, Flex } from '@chakra-ui/react'
@@ -147,11 +148,16 @@ const PaymentButtonGroup = ({
handleBuilderClick(false)
}
}, [handleBuilderClick, isMobile])
+ const { t } = useTranslation()
const {
deletePaymentModalDisclosure: { onOpen: onDeleteModalOpen },
} = useBuilderAndDesignContext()
+ const { deleteField, editField } = t('features.common.tooltip', {
+ returnObjects: true,
+ })
+
return (
}
onClick={handleEditFieldClick}
/>
)}
-
+
}
onClick={onDeleteModalOpen}
/>
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/StartPageView.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/StartPageView.tsx
index 87c7dc52e3..acb96de6a8 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/StartPageView.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/StartPageView.tsx
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
import { BiCog } from 'react-icons/bi'
import { Box, ButtonGroup, Collapse, Flex, IconButton } from '@chakra-ui/react'
@@ -37,6 +38,7 @@ import {
} from '../useFieldBuilderStore'
export const StartPageView = () => {
+ const { t } = useTranslation()
const isMobile = useIsMobile()
const { data: form, isLoading } = useCreateTabForm()
const setFieldBuilderToInactive = useFieldBuilderStore(
@@ -259,7 +261,7 @@ export const StartPageView = () => {
}
onClick={handleEditInstructionsClick}
/>
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.stories.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.stories.tsx
index f8c23f7795..a3c0c6195d 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.stories.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.stories.tsx
@@ -1,6 +1,13 @@
import { Meta, StoryFn } from '@storybook/react'
-import { BasicField, DropdownFieldBase } from '~shared/types'
+import {
+ BasicField,
+ DropdownFieldBase,
+ FormFieldDto,
+ FormResponseMode,
+ FormWorkflowStepDto,
+ WorkflowType,
+} from '~shared/types'
import { createFormBuilderMocks } from '~/mocks/msw/handlers/admin-form'
@@ -8,7 +15,9 @@ import { EditFieldDrawerDecorator, StoryRouter } from '~utils/storybook'
import { EditDropdown } from './EditDropdown'
-const DEFAULT_DROPDOWN_FIELD: DropdownFieldBase = {
+const DEFAULT_DROPDOWN_FIELD: DropdownFieldBase & {
+ _id: FormFieldDto['_id']
+} = {
title: 'Storybook Dropdown',
description: 'Some description about Dropdown',
required: true,
@@ -16,6 +25,7 @@ const DEFAULT_DROPDOWN_FIELD: DropdownFieldBase = {
fieldType: BasicField.Dropdown,
fieldOptions: ['Option 1', 'Option 2', 'Option 3'],
globalId: 'unused',
+ _id: 'dropdown_field_id',
}
export default {
@@ -39,7 +49,9 @@ export default {
} as Meta
interface StoryArgs {
- field: DropdownFieldBase
+ field: DropdownFieldBase & {
+ _id: FormFieldDto['_id']
+ }
}
const Template: StoryFn = ({ field }) => {
@@ -48,3 +60,28 @@ const Template: StoryFn = ({ field }) => {
export const Default = Template.bind({})
Default.storyName = 'EditDropdown'
+
+const workflow_step_1: FormWorkflowStepDto = {
+ _id: '61e6857c9c794b0012f1c6f8',
+ workflow_type: WorkflowType.Static,
+ emails: [],
+ edit: [],
+}
+
+const workflow_step_2: FormWorkflowStepDto = {
+ _id: '61e6857c9c794b0012f1c6f8',
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: DEFAULT_DROPDOWN_FIELD._id,
+ edit: [],
+}
+
+export const FieldUsedForConditionalRouting = Template.bind({})
+FieldUsedForConditionalRouting.parameters = {
+ msw: createFormBuilderMocks(
+ {
+ responseMode: FormResponseMode.Multirespondent,
+ workflow: [workflow_step_1, workflow_step_2],
+ },
+ 0,
+ ),
+}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.tsx
index 91e7a78999..6767c16486 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditDropdown/EditDropdown.tsx
@@ -3,15 +3,18 @@ import { useTranslation } from 'react-i18next'
import { FormControl } from '@chakra-ui/react'
import { extend, pick } from 'lodash'
+import { WorkflowType } from '~shared/types'
import { DropdownFieldBase } from '~shared/types/field'
import { createBaseValidationRules } from '~utils/fieldValidation'
import FormErrorMessage from '~components/FormControl/FormErrorMessage'
import FormLabel from '~components/FormControl/FormLabel'
+import InlineMessage from '~components/InlineMessage'
import Input from '~components/Input'
import Textarea from '~components/Textarea'
import Toggle from '~components/Toggle'
+import { useAdminFormWorkflow } from '../../../../../../create/workflow/hooks/useAdminFormWorkflow'
import { CreatePageDrawerContentContainer } from '../../../../../common'
import {
SPLIT_TEXTAREA_TRANSFORM,
@@ -51,6 +54,7 @@ const transformDropdownEditFormToField = (
export const EditDropdown = ({ field }: EditDropdownProps): JSX.Element => {
const { t } = useTranslation()
+ const { formWorkflow } = useAdminFormWorkflow()
const {
register,
formState: { errors },
@@ -72,6 +76,14 @@ export const EditDropdown = ({ field }: EditDropdownProps): JSX.Element => {
[],
)
+ const isFieldUsedForConditionalRouting = useMemo(() => {
+ return formWorkflow?.some(
+ (workflow) =>
+ workflow.workflow_type === WorkflowType.Conditional &&
+ workflow.conditional_field === field._id,
+ )
+ }, [formWorkflow, field._id])
+
return (
@@ -106,6 +118,13 @@ export const EditDropdown = ({ field }: EditDropdownProps): JSX.Element => {
{errors?.fieldOptionsString?.message}
+ {isFieldUsedForConditionalRouting ? (
+
+ This field is linked to a step in your workflow. If you make any
+ changes, ensure that the options and emails are updated in your CSV
+ file.
+
+ ) : null}
{
},
})
- function conditionallyDisplayInfoBox() {
- const currentDate = new Date().toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- const targetDate = new Date('2024-06-28T00:00:00').toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
-
- if (new Date(currentDate) <= new Date(targetDate)) {
- return (
- <>
-
-
- To align with MyInfo terminology, the “Gender” field will be
- renamed to “Sex” from 28 Jun 2024.
-
- {' '}
- >
- )
- } else {
- return null
- }
- }
-
return (
- {conditionallyDisplayInfoBox()}
Data source
{extendedField.dataSource.map((dataSource, idx) => (
diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx
index 367dfbc862..a93ba49f0c 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignDrawer/EditFieldDrawer/edit-fieldtype/EditMyInfoChildren/EditMyInfoChildren.tsx
@@ -61,34 +61,9 @@ export const EditMyInfoChildren = ({
},
})
- function conditionallyDisplayInfoBox() {
- const currentDate = new Date().toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- const targetDate = new Date('2024-06-28T00:00:00').toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
-
- if (new Date(currentDate) <= new Date(targetDate)) {
- return (
- <>
-
-
- To align with MyInfo terminology, the “Gender” field will be
- renamed to “Sex” from 28 Jun 2024.
-
- {' '}
- >
- )
- } else {
- return null
- }
- }
-
return (
- {conditionallyDisplayInfoBox()}
Data source
{extendedField.dataSource.map((dataSource, idx) => (
diff --git a/frontend/src/features/admin-form/create/builder-and-design/DeleteFieldModal/DeleteFieldModal.tsx b/frontend/src/features/admin-form/create/builder-and-design/DeleteFieldModal/DeleteFieldModal.tsx
index 996c22edf9..127a86d5c8 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/DeleteFieldModal/DeleteFieldModal.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/DeleteFieldModal/DeleteFieldModal.tsx
@@ -58,21 +58,20 @@ export const DeleteFieldModal = (): JSX.Element => {
}
}, [deleteFieldMutation, onClose, stateData])
+ const {
+ title,
+ description: { field, logic },
+ confirmButtonText,
+ } = t('features.adminForm.modals.deleteField', { returnObjects: true })
+
return (
- Delete field
+ {title}
-
- {fieldIsInLogic
- ? `This field is used in your form logic, so deleting it may cause
- your logic to stop working correctly. Are you sure you want to
- delete this field?`
- : `Are you sure you want to delete this field? This action
- cannot be undone.`}
-
+ {fieldIsInLogic ? logic : field}
{
onClick={handleDeleteConfirmation}
isLoading={deleteFieldMutation.isLoading}
>
- Yes, delete field
+ {confirmButtonText}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/DeletePaymentModal/DeletePaymentModal.tsx b/frontend/src/features/admin-form/create/builder-and-design/DeletePaymentModal/DeletePaymentModal.tsx
index 733369b8be..2027fa4a3c 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/DeletePaymentModal/DeletePaymentModal.tsx
+++ b/frontend/src/features/admin-form/create/builder-and-design/DeletePaymentModal/DeletePaymentModal.tsx
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
import {
ButtonGroup,
Modal,
@@ -28,6 +29,7 @@ export const DeletePaymentModal = (): JSX.Element => {
const {
deletePaymentModalDisclosure: { onClose },
} = useBuilderAndDesignContext()
+ const { t } = useTranslation()
const { deletePaymentFieldMutation } = useDeleteFormField()
@@ -44,29 +46,32 @@ export const DeletePaymentModal = (): JSX.Element => {
}
}, [deletePaymentFieldMutation, onClose, stateData, setFieldListTabIndex])
+ const {
+ title,
+ description: { payment: description },
+ confirmButtonText,
+ } = t('features.adminForm.modals.deleteField', { returnObjects: true })
+
return (
- Delete field
+ {title}
-
- Are you sure you want to delete payment field? This action can't be
- undone.
-
+ {description}
diff --git a/frontend/src/features/admin-form/create/builder-and-design/UpdateFormFieldService.ts b/frontend/src/features/admin-form/create/builder-and-design/UpdateFormFieldService.ts
index 53ff0d9349..9c087f29c9 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/UpdateFormFieldService.ts
+++ b/frontend/src/features/admin-form/create/builder-and-design/UpdateFormFieldService.ts
@@ -1,4 +1,8 @@
-import { FieldCreateDto, FormFieldDto } from '~shared/types/field'
+import {
+ DropdownFieldBase,
+ FieldCreateDto,
+ FormFieldDto,
+} from '~shared/types/field'
import { transformAllIsoStringsToDate } from '~utils/date'
import { ApiService } from '~services/ApiService'
@@ -45,6 +49,23 @@ export const updateSingleFormField = async ({
.then(transformAllIsoStringsToDate)
}
+export const updateOptionsToRecipientsMap = async ({
+ formId,
+ fieldId,
+ optionsToRecipientsMap,
+}: {
+ formId: string
+ fieldId: string
+ optionsToRecipientsMap: DropdownFieldBase['optionsToRecipientsMap']
+}): Promise => {
+ return ApiService.put(
+ `${ADMIN_FORM_ENDPOINT}/${formId}/fields/${fieldId}/options-to-recipients-map`,
+ { optionsToRecipientsMap },
+ )
+ .then(({ data }) => data)
+ .then(transformAllIsoStringsToDate)
+}
+
/**
* Reorders the field to the given new position.
* @param formId the id of the form to perform the field reorder
diff --git a/frontend/src/features/admin-form/create/builder-and-design/mutations/useCreateFormField.ts b/frontend/src/features/admin-form/create/builder-and-design/mutations/useCreateFormField.ts
index 8bd5c43595..ab27216ed4 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/mutations/useCreateFormField.ts
+++ b/frontend/src/features/admin-form/create/builder-and-design/mutations/useCreateFormField.ts
@@ -1,4 +1,5 @@
import { useCallback, useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
import { useMutation, useQueryClient } from 'react-query'
import { useParams } from 'react-router-dom'
@@ -22,6 +23,7 @@ import {
} from '../utils/getMutationMessage'
export const useCreateFormField = () => {
+ const { t } = useTranslation()
const { formId } = useParams()
if (!formId) throw new Error('No formId provided')
@@ -45,15 +47,14 @@ export const useCreateFormField = () => {
if (stateData.state !== FieldBuilderState.CreatingField) {
toast({
status: 'warning',
- description:
- 'Something went wrong when creating your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.create.error'),
})
return
}
toast({
- description: `The ${getMutationToastDescriptionFieldName(
- newField,
- )} was created.`,
+ description: t('features.adminForm.toasts.field.create.success', {
+ field: getMutationToastDescriptionFieldName(newField),
+ }),
})
queryClient.setQueryData(adminFormKey, (oldForm) => {
// Should not happen, should not be able to update field if there is no
@@ -65,7 +66,7 @@ export const useCreateFormField = () => {
// Switch from creation to editing
updateEditState(newField)
},
- [adminFormKey, stateData, queryClient, updateEditState, toast],
+ [adminFormKey, stateData, queryClient, updateEditState, toast, t],
)
const handleError = useCallback(
diff --git a/frontend/src/features/admin-form/create/builder-and-design/mutations/useDeleteFormField.ts b/frontend/src/features/admin-form/create/builder-and-design/mutations/useDeleteFormField.ts
index 151cf696f9..0267708698 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/mutations/useDeleteFormField.ts
+++ b/frontend/src/features/admin-form/create/builder-and-design/mutations/useDeleteFormField.ts
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
import { useMutation, useQueryClient } from 'react-query'
import { useParams } from 'react-router-dom'
@@ -34,6 +35,7 @@ import {
} from '../utils/getMutationMessage'
export const useDeleteFormField = () => {
+ const { t } = useTranslation()
const { formId } = useParams()
if (!formId) throw new Error('No formId provided')
@@ -66,15 +68,14 @@ export const useDeleteFormField = () => {
if (stateData.state !== FieldBuilderState.EditingField) {
toast({
status: 'warning',
- description:
- 'Something went wrong when deleting your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.delete.error'),
})
return
}
toast({
- description: `The ${getMutationToastDescriptionFieldName(
- stateData.field,
- )} was deleted.`,
+ description: t('features.adminForm.toasts.field.delete.success', {
+ field: getMutationToastDescriptionFieldName(stateData.field),
+ }),
})
queryClient.setQueryData(adminFormKey, (oldForm) => {
// Should not happen, should not be able to update field if there is no
@@ -86,8 +87,7 @@ export const useDeleteFormField = () => {
if (deletedFieldIndex < 0) {
toast({
status: 'warning',
- description:
- 'Something went wrong when deleting your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.delete.error'),
})
} else {
oldForm.form_fields.splice(deletedFieldIndex, 1)
@@ -95,7 +95,7 @@ export const useDeleteFormField = () => {
return oldForm
})
setToInactive()
- }, [adminFormKey, stateData, queryClient, setToInactive, toast])
+ }, [adminFormKey, stateData, queryClient, setToInactive, toast, t])
const handleError = useCallback(
(error: Error) => {
@@ -116,8 +116,7 @@ export const useDeleteFormField = () => {
if (paymentState !== PaymentState.EditingPayment) {
toast({
status: 'warning',
- description:
- 'Something went wrong when deleting your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.delete.error'),
})
return
}
@@ -128,7 +127,9 @@ export const useDeleteFormField = () => {
},
)
toast({
- description: 'The payment was deleted.',
+ description: t('features.adminForm.toasts.field.delete.success', {
+ field: 'payment',
+ }),
})
setPaymentToInactive()
},
diff --git a/frontend/src/features/admin-form/create/builder-and-design/mutations/useDuplicateFormField.ts b/frontend/src/features/admin-form/create/builder-and-design/mutations/useDuplicateFormField.ts
index f7381b9859..8c559a6df6 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/mutations/useDuplicateFormField.ts
+++ b/frontend/src/features/admin-form/create/builder-and-design/mutations/useDuplicateFormField.ts
@@ -1,4 +1,5 @@
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
import { useMutation, useQueryClient } from 'react-query'
import { useParams } from 'react-router-dom'
@@ -23,6 +24,7 @@ import {
} from '../utils/getMutationMessage'
export const useDuplicateFormField = () => {
+ const { t } = useTranslation()
const { formId } = useParams()
if (!formId) throw new Error('No formId provided')
const fieldBuilderState = useFieldBuilderStore(fieldBuilderStateSelector)
@@ -40,20 +42,20 @@ export const useDuplicateFormField = () => {
if (fieldBuilderState !== FieldBuilderState.EditingField) {
toast({
status: 'warning',
- description:
- 'Something went wrong when creating your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.duplicate.error'),
})
return
}
toast({
- description: `The ${getMutationToastDescriptionFieldName(
- newField,
- )} was duplicated.${
+ description: t(
logicedFieldIdsSet?.has(fieldId)
- ? ' Associated logic was not duplicated.'
- : ''
- }`,
+ ? 'features.adminForm.toasts.field.duplicate.successButNoLogic'
+ : 'features.adminForm.toasts.field.duplicate.success',
+ {
+ field: getMutationToastDescriptionFieldName(newField),
+ },
+ ),
})
queryClient.setQueryData(adminFormKey, (oldForm) => {
@@ -80,6 +82,7 @@ export const useDuplicateFormField = () => {
queryClient,
adminFormKey,
updateEditState,
+ t,
],
)
diff --git a/frontend/src/features/admin-form/create/builder-and-design/mutations/useEditFormField.ts b/frontend/src/features/admin-form/create/builder-and-design/mutations/useEditFormField.ts
index 694fd7bab5..036b07c67c 100644
--- a/frontend/src/features/admin-form/create/builder-and-design/mutations/useEditFormField.ts
+++ b/frontend/src/features/admin-form/create/builder-and-design/mutations/useEditFormField.ts
@@ -1,15 +1,19 @@
import { useCallback } from 'react'
+import { useTranslation } from 'react-i18next'
import { useMutation, useQueryClient } from 'react-query'
import { useParams } from 'react-router-dom'
-import { FormFieldDto } from '~shared/types/field'
+import { DropdownFieldBase, FormFieldDto } from '~shared/types/field'
import { AdminFormDto } from '~shared/types/form'
import { useToast } from '~hooks/useToast'
import { adminFormKeys } from '~features/admin-form/common/queries'
-import { updateSingleFormField } from '../UpdateFormFieldService'
+import {
+ updateOptionsToRecipientsMap,
+ updateSingleFormField,
+} from '../UpdateFormFieldService'
import {
FieldBuilderState,
fieldBuilderStateSelector,
@@ -21,6 +25,7 @@ import {
} from '../utils/getMutationMessage'
export const useEditFormField = () => {
+ const { t } = useTranslation()
const { formId } = useParams()
if (!formId) throw new Error('No formId provided')
@@ -36,15 +41,14 @@ export const useEditFormField = () => {
if (fieldBuilderState !== FieldBuilderState.EditingField) {
toast({
status: 'warning',
- description:
- 'Something went wrong when editing your field. Please refresh and try again.',
+ description: t('features.adminForm.toasts.field.update.error'),
})
return
}
toast({
- description: `The ${getMutationToastDescriptionFieldName(
- newField,
- )} was updated.`,
+ description: t('features.adminForm.toasts.field.update.success', {
+ field: getMutationToastDescriptionFieldName(newField),
+ }),
})
queryClient.setQueryData(adminFormKey, (oldForm) => {
// Should not happen, should not be able to update field if there is no
@@ -57,7 +61,7 @@ export const useEditFormField = () => {
return oldForm
})
},
- [adminFormKey, fieldBuilderState, queryClient, toast],
+ [adminFormKey, fieldBuilderState, queryClient, toast, t],
)
const handleError = useCallback(
@@ -80,5 +84,39 @@ export const useEditFormField = () => {
onError: handleError,
},
),
+ editOptionToRecipientsMutation: useMutation(
+ ({
+ fieldId,
+ optionsToRecipientsMap,
+ }: {
+ fieldId: string
+ optionsToRecipientsMap: DropdownFieldBase['optionsToRecipientsMap']
+ }) => {
+ return updateOptionsToRecipientsMap({
+ formId,
+ fieldId,
+ optionsToRecipientsMap,
+ })
+ },
+ {
+ onSuccess: (newField: FormFieldDto) => {
+ toast.closeAll()
+ toast({
+ description: 'Your options and emails have been saved.',
+ })
+ queryClient.setQueryData(adminFormKey, (oldForm) => {
+ // Should not happen, should not be able to update field if there is no
+ // existing data.
+ if (!oldForm) throw new Error('Query should have been set')
+ const currentFieldIndex = oldForm.form_fields.findIndex(
+ (ff) => ff._id === newField._id,
+ )
+ oldForm.form_fields[currentFieldIndex] = newField
+ return oldForm
+ })
+ },
+ onError: handleError,
+ },
+ ),
}
}
diff --git a/frontend/src/features/admin-form/create/constants.ts b/frontend/src/features/admin-form/create/constants.ts
index a5690a73d8..8ceca71882 100644
--- a/frontend/src/features/admin-form/create/constants.ts
+++ b/frontend/src/features/admin-form/create/constants.ts
@@ -203,7 +203,7 @@ export const MYINFO_FIELD_TO_DRAWER_META: {
isSubmitted: true,
},
[MyInfoAttribute.Sex]: {
- label: 'Gender',
+ label: 'Sex',
icon: BiInfinite,
isSubmitted: true,
},
@@ -346,7 +346,7 @@ export const MYINFO_FIELD_TO_DRAWER_META: {
isSubmitted: true,
},
[MyInfoAttribute.ChildGender]: {
- label: 'Gender',
+ label: 'Sex',
icon: BiDummyIcon,
isSubmitted: true,
},
@@ -361,24 +361,3 @@ export const MYINFO_FIELD_TO_DRAWER_META: {
isSubmitted: true,
},
}
-// TODO: remove after 28 Jun 2024 as this would have fully taken effect
-function updateLabelsBasedOnDate() {
- const currentDate = new Date().toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- const targetDate = new Date('2024-06-28T00:00:00').toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- if (new Date(currentDate) >= new Date(targetDate)) {
- const sexAttribute = MYINFO_FIELD_TO_DRAWER_META[MyInfoAttribute.Sex]
- if (sexAttribute) {
- sexAttribute.label = 'Sex'
- }
- const childGenderAttribute =
- MYINFO_FIELD_TO_DRAWER_META[MyInfoAttribute.ChildGender]
- if (childGenderAttribute) {
- childGenderAttribute.label = 'Sex'
- }
- }
-}
-updateLabelsBasedOnDate()
diff --git a/frontend/src/features/admin-form/create/logic/components/DeleteLogicModal/DeleteLogicModal.tsx b/frontend/src/features/admin-form/create/logic/components/DeleteLogicModal/DeleteLogicModal.tsx
index 2b7e392486..b1657f4ee2 100644
--- a/frontend/src/features/admin-form/create/logic/components/DeleteLogicModal/DeleteLogicModal.tsx
+++ b/frontend/src/features/admin-form/create/logic/components/DeleteLogicModal/DeleteLogicModal.tsx
@@ -61,8 +61,8 @@ export const DeleteLogicModal = ({
Delete logic
- Are you sure you want to delete this logic? This action is not
- reversible.
+ Are you sure you want to delete this logic? This action cannot be
+ undone.
diff --git a/frontend/src/features/admin-form/create/logic/components/LogicContent/InactiveLogicBlock/FieldLogicBadge.tsx b/frontend/src/features/admin-form/create/logic/components/LogicContent/InactiveLogicBlock/FieldLogicBadge.tsx
index 9ce584bba6..bd5cdd564c 100644
--- a/frontend/src/features/admin-form/create/logic/components/LogicContent/InactiveLogicBlock/FieldLogicBadge.tsx
+++ b/frontend/src/features/admin-form/create/logic/components/LogicContent/InactiveLogicBlock/FieldLogicBadge.tsx
@@ -26,7 +26,7 @@ export const FieldLogicBadge = ({
field,
defaults = {
variant: 'error',
- message: 'This field was deleted and has been removed from your workflow',
+ message: 'This field was deleted, please select another field',
},
}: FieldLogicBadgeProps) => {
const fieldMeta = useMemo(
diff --git a/frontend/src/features/admin-form/create/workflow/CreatePageWorkflowTab.stories.tsx b/frontend/src/features/admin-form/create/workflow/CreatePageWorkflowTab.stories.tsx
index d1842724c6..678d86e81a 100644
--- a/frontend/src/features/admin-form/create/workflow/CreatePageWorkflowTab.stories.tsx
+++ b/frontend/src/features/admin-form/create/workflow/CreatePageWorkflowTab.stories.tsx
@@ -124,6 +124,37 @@ const form_field_7: FormFieldDto = {
_id: '61e6857c9c794b0012f1c6u9',
}
+const dropdown_field_valid_mapping: FormFieldDto = {
+ title: 'Department',
+ description: '',
+ required: true,
+ disabled: false,
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['Engineering', 'Design', 'Operations', 'Product'],
+ optionsToRecipientsMap: {
+ Engineering: ['kevin@example.com'],
+ Design: ['alicia@example.com'],
+ Operations: ['ruchel@example.com'],
+ Product: ['kenneth@example.com'],
+ },
+ _id: '6200e1534ad4f00012848d91',
+}
+
+const dropdown_field_missing_options_mapping: FormFieldDto = {
+ title: 'Department',
+ description: '',
+ required: true,
+ disabled: false,
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['Engineering', 'Design', 'Operations', 'Product'],
+ optionsToRecipientsMap: {
+ Engineering: ['kevin@example.com'],
+ Design: ['alicia@example.com'],
+ Operations: ['ruchel@example.com'],
+ },
+ _id: '6200e1534ad4f00012848d92',
+}
+
const workflow_step_1: FormWorkflowStepDto = {
_id: '61e6857c9c794b0012f1c6f8',
workflow_type: WorkflowType.Static,
@@ -152,6 +183,21 @@ const workflow_step_2_with_all_fields_deleted: FormWorkflowStepDto = {
edit: ['deleted_object_id_1', 'deleted_object_id_2'],
}
+const workflow_step_2_with_valid_conditional_recipient: FormWorkflowStepDto = {
+ _id: '61e6857c9c794b001ak3cnkl',
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_valid_mapping._id,
+ edit: [form_field_3._id, form_field_4._id],
+}
+
+const workflow_step_2_with_invalid_conditional_recipient: FormWorkflowStepDto =
+ {
+ _id: '61e6857c9c794b001ak3cnkl',
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_missing_options_mapping._id,
+ edit: [form_field_3._id, form_field_4._id],
+ }
+
const workflow_step_3_with_approval: FormWorkflowStepDto = {
_id: '61e6857c9c794b0012f1c6g2',
workflow_type: WorkflowType.Dynamic,
@@ -178,6 +224,8 @@ const FORM_WITH_WORKFLOW: Partial = {
form_field_5,
form_field_6,
form_field_7,
+ dropdown_field_valid_mapping,
+ dropdown_field_missing_options_mapping,
],
workflow: [workflow_step_1, workflow_step_2],
}
@@ -255,6 +303,28 @@ Step2AllFieldsDeleted.parameters = {
}),
}
+export const Step2ValidConditionalRecipientSelected = Template.bind({})
+Step2ValidConditionalRecipientSelected.parameters = {
+ msw: buildMswRoutes({
+ ...FORM_WITH_WORKFLOW,
+ workflow: [
+ workflow_step_1,
+ workflow_step_2_with_valid_conditional_recipient,
+ ],
+ }),
+}
+
+export const Step2InvalidConditionalRecipientSelected = Template.bind({})
+Step2InvalidConditionalRecipientSelected.parameters = {
+ msw: buildMswRoutes({
+ ...FORM_WITH_WORKFLOW,
+ workflow: [
+ workflow_step_1,
+ workflow_step_2_with_invalid_conditional_recipient,
+ ],
+ }),
+}
+
export const Loading = Template.bind({})
Loading.parameters = {
msw: buildMswRoutes({}, 'infinite'),
diff --git a/frontend/src/features/admin-form/create/workflow/components/DeleteStepModal/DeleteStepModal.tsx b/frontend/src/features/admin-form/create/workflow/components/DeleteStepModal/DeleteStepModal.tsx
index e85fffe4f9..75ec888fbf 100644
--- a/frontend/src/features/admin-form/create/workflow/components/DeleteStepModal/DeleteStepModal.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/DeleteStepModal/DeleteStepModal.tsx
@@ -61,8 +61,8 @@ export const DeleteStepModal = ({
Delete step
- Are you sure you want to delete this step? This action is not
- reversible.
+ Are you sure you want to delete this step? This action cannot be
+ undone.
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.stories.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.stories.tsx
index 1f586db932..0f0a646fa7 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.stories.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.stories.tsx
@@ -1,4 +1,4 @@
-import { expect, userEvent, waitFor, within } from '@storybook/test'
+import { expect, screen, userEvent, waitFor, within } from '@storybook/test'
import {
BasicField,
@@ -83,6 +83,47 @@ const form_field_5: FormFieldDto = {
allowPrefill: false,
}
+const dropdown_field_no_mapping: FormFieldDto = {
+ title: 'Department',
+ description: '',
+ required: true,
+ disabled: false,
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['Engineering', 'Design', 'Operations', 'Product'],
+ _id: '6200e1534ad4f00012848d90',
+}
+
+const dropdown_field_valid_mapping: FormFieldDto = {
+ title: 'Department',
+ description: '',
+ required: true,
+ disabled: false,
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['Engineering', 'Design', 'Operations', 'Product'],
+ optionsToRecipientsMap: {
+ Engineering: ['kevin@example.com'],
+ Design: ['alicia@example.com'],
+ Operations: ['ruchel@example.com'],
+ Product: ['kenneth@example.com'],
+ },
+ _id: '6200e1534ad4f00012848d91',
+}
+
+const dropdown_field_missing_options_mapping: FormFieldDto = {
+ title: 'Department',
+ description: '',
+ required: true,
+ disabled: false,
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['Engineering', 'Design', 'Operations', 'Product'],
+ optionsToRecipientsMap: {
+ Engineering: ['kevin@example.com'],
+ Design: ['alicia@example.com'],
+ Operations: ['ruchel@example.com'],
+ },
+ _id: '6200e1534ad4f00012848d92',
+}
+
const mrfFormViewWithFields = [
getAdminFormView({
mode: FormResponseMode.Multirespondent,
@@ -93,6 +134,9 @@ const mrfFormViewWithFields = [
form_field_3,
form_field_4,
form_field_5,
+ dropdown_field_no_mapping,
+ dropdown_field_valid_mapping,
+ dropdown_field_missing_options_mapping,
],
},
}),
@@ -178,9 +222,275 @@ export const Step2FixedEmailEmpty = {
},
)
},
+ args: {
+ stepNumber: 1,
+ },
+}
+
+export const Step2ConditionalRoutingEmpty = {
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ },
+}
+
+export const Step2ConditionalRouting = {
+ // due to the double registration of 'workflow_type' there would be a weird interaction
+ // where the default value will be reset
+ // thus we have to manually select the field again
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_no_mapping._id,
+ },
+ },
+}
+
+export const Step2ConditionalRoutingValidOptionsUploaded = {
+ // due to the double registration of 'workflow_type' there would be a weird interaction
+ // where the default value will be reset
+ // thus we have to manually select the field again
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_valid_mapping._id,
+ },
+ },
+}
+
+export const Step2ConditionalRoutingDeleteWarningModal = {
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ const deleteButton = await canvas.getByLabelText('Click to remove file')
+ await userEvent.click(deleteButton)
+ },
+ { timeout: 5000 },
+ )
+ // Assert the modal appears
+ await waitFor(
+ async () => {
+ expect(await screen.getByText('Delete CSV file')).toBeInTheDocument()
+ },
+ { timeout: 5000 },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_valid_mapping._id,
+ },
+ },
+}
+
+export const Step2ConditionalRoutingInvalidOptionsUploadedErrorMessage = {
+ // due to the double registration of 'workflow_type' there would be a weird interaction
+ // where the default value will be reset
+ // thus we have to manually select the field again
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_missing_options_mapping._id,
+ },
+ },
+}
+
+export const Step2ConditionalRoutingNoFieldSelectedErrorMessage = {
+ // due to the double registration of 'workflow_type' there would be a weird interaction
+ // where the default value will be reset
+ // thus we have to manually select the field again
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () =>
+ expect(await canvas.getByText('Save step')).not.toBeDisabled(),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ const saveStep = canvas.getByText('Save step')
+ expect(saveStep).not.toBeDisabled()
+ await userEvent.click(saveStep)
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
+ args: {
+ stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ },
+ },
+}
+export const Step2ConditionalRoutingNoOptionsToReicipientsMapErrorMessage = {
+ // due to the double registration of 'workflow_type' there would be a weird interaction
+ // where the default value will be reset
+ // thus we have to manually select the field again
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
+ const canvas = within(canvasElement)
+ await waitFor(
+ async () => await userEvent.click(canvas.getByText('Save step')),
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await userEvent.click(
+ await canvas.getByText(
+ 'Emails assigned to options in a dropdown field',
+ ),
+ )
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ const saveStep = canvas.getByText('Save step')
+ expect(saveStep).not.toBeDisabled()
+ await userEvent.click(saveStep)
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ },
args: {
stepNumber: 1,
+ defaultValues: {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: dropdown_field_no_mapping._id,
+ },
},
}
@@ -207,7 +517,7 @@ export const Step3AllSelectedValid = {
args: {
stepNumber: 2,
defaultValues: {
- workflow_type: WorkflowType.Dynamic,
+ workflow_type: WorkflowType.Static,
field: form_field_4._id,
edit: [form_field_1._id, form_field_5._id],
approval_field: form_field_1._id,
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.tsx
index 55a4927911..17b519a200 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.tsx
@@ -2,7 +2,11 @@ import { useLayoutEffect, useRef } from 'react'
import { useForm } from 'react-hook-form'
import { Box, Divider, Stack } from '@chakra-ui/react'
-import { FormWorkflowStep, WorkflowType } from '~shared/types'
+import {
+ FormWorkflowStep,
+ FormWorkflowStepBase,
+ WorkflowType,
+} from '~shared/types'
import { SaveActionGroup } from '~features/admin-form/create/logic/components/LogicContent/EditLogicBlock/EditCondition'
import { useUser } from '~features/user/queries'
@@ -82,11 +86,19 @@ export const EditStepBlock = ({
})
}
- let step: FormWorkflowStep
+ let step: FormWorkflowStep & { _id: string }
+
+ const workflowStepBase: FormWorkflowStepBase & { _id: string } = {
+ _id: inputs._id,
+ workflow_type: inputs.workflow_type,
+ edit: inputs.edit,
+ approval_field: inputs.approval_field,
+ }
+
switch (inputs.workflow_type) {
case WorkflowType.Static: {
step = {
- ...inputs,
+ ...workflowStepBase,
// Need to explicitly set workflow_type in this object to help with typechecking.
workflow_type: WorkflowType.Static,
emails: inputs.emails ?? [],
@@ -96,15 +108,22 @@ export const EditStepBlock = ({
case WorkflowType.Dynamic: {
if (!inputs.field) return
step = {
- ...inputs,
+ ...workflowStepBase,
workflow_type: WorkflowType.Dynamic,
field: inputs.field,
}
break
}
+ case WorkflowType.Conditional: {
+ if (!inputs.conditional_field) return
+ step = {
+ ...workflowStepBase,
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: inputs.conditional_field,
+ }
+ break
+ }
default: {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const _: never = inputs.workflow_type
throw new Error('Invalid workflow type')
}
}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock.tsx
deleted file mode 100644
index a376d090d1..0000000000
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-import { Controller, UseFormReturn } from 'react-hook-form'
-import { As, FormControl, Stack, Text } from '@chakra-ui/react'
-import { get } from 'lodash'
-import isEmail from 'validator/lib/isEmail'
-
-import { UserDto, WorkflowType } from '~shared/types'
-
-import { textStyles } from '~theme/textStyles'
-import { SingleSelect } from '~components/Dropdown'
-import FormErrorMessage from '~components/FormControl/FormErrorMessage'
-import FormLabel from '~components/FormControl/FormLabel'
-import Radio from '~components/Radio'
-import { TagInput } from '~components/TagInput'
-
-import { BASICFIELD_TO_DRAWER_META } from '~features/admin-form/create/constants'
-import { EditStepInputs } from '~features/admin-form/create/workflow/types'
-
-import { useAdminFormWorkflow } from '../../../hooks/useAdminFormWorkflow'
-import { isFirstStepByStepNumber } from '../utils/isFirstStepByStepNumber'
-
-import { EditStepBlockContainer } from './EditStepBlockContainer'
-
-const WORKFLOW_TYPE_VALIDATION = {
- required: 'Please select a respondent type',
- validate: (value: WorkflowType) => {
- if (![WorkflowType.Static, WorkflowType.Dynamic].includes(value)) {
- return 'The selected respondent type is invalid'
- }
- },
-}
-
-interface RespondentOptionProps {
- isLoading: boolean
- formMethods: UseFormReturn
- selectedWorkflowType: WorkflowType
-}
-
-const StaticRespondentOption = ({
- isLoading,
- formMethods,
- selectedWorkflowType,
-}: RespondentOptionProps) => {
- const {
- register,
- control,
- formState: { errors },
- } = formMethods
- const staticTagInputErrorMessage = get(errors, 'emails.message')
-
- return (
- <>
-
- Specific email(s)
- {selectedWorkflowType === WorkflowType.Static ? (
-
-
- !emails || emails.length === 0
- ? 'You must enter at least one email to receive responses'
- : true,
- isEmails: (emails) =>
- !emails ||
- emails.every((email) => isEmail(email)) ||
- 'Please enter valid email(s) (e.g. me@example.com) separated by commas, as invalid emails will not be saved',
- },
- }}
- render={({ field }) => (
-
- )}
- />
- {staticTagInputErrorMessage}
- {!staticTagInputErrorMessage ? (
-
- Separate multiple emails with a comma
-
- ) : null}
-
- ) : null}
-
- >
- )
-}
-
-interface DynamicRespondentOptionProps extends RespondentOptionProps {
- emailFieldItems: {
- label: string
- value: string
- icon?: As
- }[]
-}
-
-const DynamicRespondentOption = ({
- isLoading,
- selectedWorkflowType,
- formMethods,
- emailFieldItems,
-}: DynamicRespondentOptionProps) => {
- const {
- register,
- formState: { errors },
- control,
- } = formMethods
-
- return (
- <>
-
- An email field from the form
- {selectedWorkflowType === WorkflowType.Dynamic ? (
-
- {
- return (
- isLoading ||
- !emailFieldItems ||
- emailFieldItems.some(
- ({ value: fieldValue }) => fieldValue === selectedValue,
- ) ||
- 'Field is not an email field'
- )
- },
- }}
- render={({ field: { value = '', ...rest } }) => (
- <>
-
- >
- )}
- />
- {errors.field?.message}
-
- ) : null}
-
- >
- )
-}
-
-interface RespondentBlockProps {
- stepNumber: number
- isLoading: boolean
- formMethods: UseFormReturn
- user: UserDto | undefined
-}
-
-export const RespondentBlock = ({
- stepNumber,
- isLoading,
- formMethods,
-}: RespondentBlockProps): JSX.Element => {
- const {
- formState: { errors },
- watch,
- } = formMethods
-
- const { emailFormFields = [] } = useAdminFormWorkflow()
-
- const emailFieldItems = emailFormFields.map(
- ({ _id, questionNumber, title, fieldType }) => ({
- label: `${questionNumber}. ${title}`,
- value: _id,
- icon: BASICFIELD_TO_DRAWER_META[fieldType].icon,
- }),
- )
-
- const selectedWorkflowType = watch('workflow_type')
-
- const isFirstStep = isFirstStepByStepNumber(stepNumber)
-
- return (
-
- {isFirstStep ? (
-
- Respondent in this step
- Anyone who has access to your form
-
- ) : (
-
- Select a respondent
-
-
-
-
-
-
- {errors.workflow_type?.message}
-
- )}
-
- )
-}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingMappingDeleteModal.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingMappingDeleteModal.tsx
new file mode 100644
index 0000000000..8b5b1c60fc
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingMappingDeleteModal.tsx
@@ -0,0 +1,46 @@
+import {
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+} from '@chakra-ui/react'
+
+import { NextAndBackButtonGroup } from '~components/Button/NextAndBackButtonGroup'
+
+interface ConditionalRoutingMappingDeleteModalProps {
+ isOpen: boolean
+ onClose: () => void
+ handleDelete: () => void
+}
+
+export const ConditionalRoutingMappingDeleteModal = ({
+ isOpen,
+ onClose,
+ handleDelete,
+}: ConditionalRoutingMappingDeleteModalProps) => {
+ return (
+
+
+
+
+ Delete CSV file
+
+ Are you sure you want to delete this CSV file? This action cannot be
+ undone.
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOption.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOption.tsx
new file mode 100644
index 0000000000..53e32ff692
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOption.tsx
@@ -0,0 +1,464 @@
+import { useEffect, useMemo, useState } from 'react'
+import { Controller, useForm } from 'react-hook-form'
+import { BiPlus } from 'react-icons/bi'
+import { useParams } from 'react-router'
+import {
+ Button,
+ FormControl,
+ Stack,
+ Text,
+ useDisclosure,
+} from '@chakra-ui/react'
+import Papa from 'papaparse'
+import isEmail from 'validator/lib/isEmail'
+
+import {
+ CONDITIONAL_ROUTING_CSV_PARSE_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_DUPLICATE_OPTIONS_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
+} from '~shared/constants/errors'
+import { DropdownFieldBase, FormFieldDto, WorkflowType } from '~shared/types'
+import { checkIsOptionsMismatched } from '~shared/utils/options-recipients-map-validation'
+
+import { parseCsvFileToCsvString } from '~utils/parseCsvFileToCsvString'
+import { SingleSelect } from '~components/Dropdown'
+import Attachment from '~components/Field/Attachment'
+import { downloadFile } from '~components/Field/Attachment/utils/downloadFile'
+import FormErrorMessage from '~components/FormControl/FormErrorMessage'
+import Radio from '~components/Radio'
+
+import { useEditFormField } from '~features/admin-form/create/builder-and-design/mutations/useEditFormField'
+import { BASICFIELD_TO_DRAWER_META } from '~features/admin-form/create/constants'
+import { FormFieldWithQuestionNo } from '~features/form/types'
+
+import { WORKFLOW_TYPE_VALIDATION } from './common'
+import { ConditionalRoutingMappingDeleteModal } from './ConditionalRoutingMappingDeleteModal'
+import { ConditionalRoutingOptionModal } from './ConditionalRoutingOptionModal'
+import { RespondentOptionProps } from './types'
+
+interface ConditionalRoutingOptionProps extends RespondentOptionProps {
+ conditionalFormFields: FormFieldWithQuestionNo<
+ FormFieldDto
+ >[]
+}
+
+export interface ConditionalRoutingConfig {
+ csvFile: File | null
+}
+
+/**
+ * Converts a CSV file into a string, validating that it has the required csv template file headers.
+ * @param csvFile - The CSV file to parse
+ * @returns A promise that resolves to the CSV content as a string
+ * @throws Error if CSV headers are invalid (must have 'Options' and 'Add emails in this column' columns)
+ */
+const parseCsvTemplateToString = async (csvFile: File) =>
+ parseCsvFileToCsvString(csvFile, (headerRow) => {
+ return {
+ isValid:
+ headerRow &&
+ headerRow.length === 2 &&
+ headerRow[0] === 'Options' &&
+ headerRow[1] === 'Add emails in this column',
+ invalidReason:
+ 'Your CSV file should only contain 2 columns with the headers "Options" and "Add emails in this column".',
+ }
+ })
+
+const getFileName = (
+ formId: string | undefined,
+ fieldTitle: string | undefined,
+) =>
+ `conditional_routing_form_${formId ?? ''}_field_${fieldTitle ?? ''}_mapping.csv`
+
+export const ConditionalRoutingOption = ({
+ isLoading,
+ formMethods,
+ selectedWorkflowType,
+ conditionalFormFields,
+}: ConditionalRoutingOptionProps) => {
+ const [isDeleteConfirmModalOpen, setIsDeleteConfirmModalOpen] =
+ useState(false)
+ const {
+ register,
+ control,
+ watch,
+ getValues,
+ formState: { errors },
+ clearErrors,
+ } = formMethods
+
+ const { formId } = useParams()
+
+ const conditionalFieldItems = conditionalFormFields.map(
+ ({ _id, questionNumber, title, fieldType }) => ({
+ label: `${questionNumber}. ${title}`,
+ value: _id,
+ icon: BASICFIELD_TO_DRAWER_META[fieldType].icon,
+ }),
+ )
+
+ const [csvFile, setCsvFile] = useState(null)
+
+ const {
+ control: conditionalRoutingConfigControl,
+ formState: { errors: conditionalRoutingConfigErrors },
+ watch: watchConditionalRoutingConfig,
+ handleSubmit,
+ } = useForm()
+
+ const selectedConditionalFieldId = watch('conditional_field')
+ const selectedConditionalField = conditionalFormFields.find(
+ (field) => field._id === selectedConditionalFieldId,
+ )
+ const isSelectedConditionalFieldFound = !!selectedConditionalField
+ const selectedConditionalFieldTitle = selectedConditionalField?.title
+ const selectedConditionalFieldOptionsToRecipientsMap =
+ selectedConditionalField?.optionsToRecipientsMap
+
+ const isOptionsToRecipientsMapAttached = !!csvFile
+
+ const standardCsvDownloadFileName = getFileName(
+ formId,
+ selectedConditionalFieldTitle,
+ )
+
+ const placeholderOptionToEmailMappingCsvFile = useMemo(
+ () =>
+ ({
+ name: standardCsvDownloadFileName,
+ type: 'text/csv',
+ }) as File,
+ [standardCsvDownloadFileName],
+ )
+
+ useEffect(() => {
+ if (selectedConditionalFieldOptionsToRecipientsMap) {
+ setCsvFile(placeholderOptionToEmailMappingCsvFile)
+ } else {
+ setCsvFile(null)
+ }
+ }, [
+ setCsvFile,
+ placeholderOptionToEmailMappingCsvFile,
+ selectedConditionalFieldOptionsToRecipientsMap,
+ ])
+
+ const { isOpen, onOpen, onClose } = useDisclosure()
+
+ const handleCsvDownload = () => {
+ if (!selectedConditionalFieldOptionsToRecipientsMap) return
+ const csvData = {
+ fields: ['Options', 'Add emails in this column'],
+ data: Object.entries(selectedConditionalFieldOptionsToRecipientsMap).map(
+ ([option, recipients]) => [option, recipients.join(',')],
+ ),
+ }
+
+ const csvString = Papa.unparse(csvData, {
+ header: true,
+ delimiter: ',',
+ skipEmptyLines: 'greedy',
+ })
+ const csvBlob = new Blob([csvString], {
+ type: 'text/csv',
+ })
+ const csvFile = new File([csvBlob], standardCsvDownloadFileName, {
+ type: 'text/csv',
+ })
+ downloadFile(csvFile)
+ }
+
+ const handleSkeletonCsvDownload =
+ (formId: string = '') =>
+ () => {
+ const getFieldOptions = (conditionalFieldId: string) => {
+ const conditionalField = conditionalFormFields.find(
+ (field) => field._id === conditionalFieldId,
+ )
+ return conditionalField?.fieldOptions
+ }
+ const generateCsvContent = (fieldOptions: string[] | undefined) => {
+ const headerRow = ['Options', 'Add emails in this column']
+ const optionsRows = fieldOptions?.map((field) => [field, '']) ?? []
+ const jsonContent = [headerRow, ...optionsRows]
+ return Papa.unparse(jsonContent, {
+ header: true,
+ delimiter: ',',
+ })
+ }
+
+ const csvStringToFile = (csvString: string, downloadFileName: string) => {
+ const csvBlob = new Blob([csvString], {
+ type: 'text/csv',
+ })
+ const csvFile = new File([csvBlob], downloadFileName, {
+ type: 'text/csv',
+ })
+ return csvFile
+ }
+
+ if (!selectedConditionalFieldId) return
+
+ const fieldOptions = getFieldOptions(selectedConditionalFieldId)
+ const csvContent = generateCsvContent(fieldOptions)
+ const csvFile = csvStringToFile(
+ csvContent,
+ getFileName(formId, selectedConditionalFieldTitle),
+ )
+ downloadFile(csvFile)
+ }
+
+ const { editOptionToRecipientsMutation } = useEditFormField()
+
+ const handleConditionalRoutingConfigSubmit =
+ (conditionalFieldId: string | undefined) =>
+ async (data: ConditionalRoutingConfig) => {
+ if (!(data.csvFile && conditionalFieldId)) {
+ return
+ }
+
+ const conditionalRoutingCsvString = await parseCsvTemplateToString(
+ data.csvFile,
+ ).catch(() => {
+ return null
+ })
+
+ if (!conditionalRoutingCsvString) return
+ const csvToOptionsToRecipientsMap = (csvString: string) => {
+ const csvRows = csvString.split('\r\n')
+ return csvRows.reduce((acc, row) => {
+ const [option, ...recipients] = row.split(',')
+ return {
+ ...acc,
+ [option]: recipients.map((email) => email.trim()),
+ }
+ }, {})
+ }
+
+ editOptionToRecipientsMutation.mutate(
+ {
+ fieldId: conditionalFieldId,
+ optionsToRecipientsMap: csvToOptionsToRecipientsMap(
+ conditionalRoutingCsvString,
+ ),
+ },
+ {
+ onSuccess: () => {
+ setCsvFile(placeholderOptionToEmailMappingCsvFile)
+ clearErrors('conditional_field')
+ onClose()
+ },
+ },
+ )
+ }
+
+ const removeOptionsToRecipientsMapping = () => {
+ if (!selectedConditionalField) return
+ editOptionToRecipientsMutation.mutate(
+ {
+ fieldId: selectedConditionalField._id,
+ optionsToRecipientsMap: {},
+ },
+ {
+ onSuccess: () => {
+ setCsvFile(null)
+ setIsDeleteConfirmModalOpen(false)
+ },
+ },
+ )
+ }
+
+ const validateCsvOptionsWithFieldOptions = (
+ optionsToRecipientsMapOptions: string[],
+ selectedConditionalFieldOptions: string[],
+ ) => {
+ if (optionsToRecipientsMapOptions.length <= 0) {
+ return
+ }
+ if (
+ checkIsOptionsMismatched(
+ optionsToRecipientsMapOptions,
+ selectedConditionalFieldOptions,
+ )
+ ) {
+ return CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE
+ }
+ }
+
+ const validateOptionsToRecipientsMapErrorMessage =
+ validateCsvOptionsWithFieldOptions(
+ [...Object.keys(selectedConditionalFieldOptionsToRecipientsMap || {})],
+ selectedConditionalField?.fieldOptions || [],
+ )
+
+ const noEmailToOptionsMappingErrorMessage =
+ !selectedConditionalFieldOptionsToRecipientsMap
+ ? 'You must add emails to options before saving this step.'
+ : null
+
+ const validateCsvFile = async (
+ file: File | null,
+ ): Promise => {
+ if (!file) return 'Please upload a CSV file'
+
+ let conditionalRoutingCsvString
+ try {
+ conditionalRoutingCsvString = await parseCsvTemplateToString(file)
+ } catch (error) {
+ if (error instanceof Error) {
+ return error.message
+ }
+ return CONDITIONAL_ROUTING_CSV_PARSE_ERROR_MESSAGE
+ }
+
+ const options = conditionalRoutingCsvString.split('\r\n')
+ const optionsSet = new Set()
+
+ for (const row of options) {
+ const [option, ...recipients] = row.split(',')
+ if (recipients.length <= 0 || !recipients[0] || !option) {
+ return CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE
+ }
+ if (recipients.some((recipient) => !isEmail(recipient.trim()))) {
+ return CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE
+ }
+ optionsSet.add(option)
+ }
+
+ const selectedConditionalFieldOptions =
+ selectedConditionalField?.fieldOptions
+
+ if (optionsSet.size < options.length) {
+ return CONDITIONAL_ROUTING_DUPLICATE_OPTIONS_ERROR_MESSAGE
+ }
+
+ return validateCsvOptionsWithFieldOptions(
+ [...optionsSet],
+ selectedConditionalFieldOptions || [],
+ )
+ }
+
+ return (
+ <>
+ setIsDeleteConfirmModalOpen(false)}
+ handleDelete={removeOptionsToRecipientsMapping}
+ />
+
+
+
+ Emails assigned to options in a dropdown field
+ {selectedWorkflowType === WorkflowType.Conditional ? (
+
+
+ {
+ if (noEmailToOptionsMappingErrorMessage) {
+ return noEmailToOptionsMappingErrorMessage
+ }
+ if (validateOptionsToRecipientsMapErrorMessage) {
+ return validateOptionsToRecipientsMapErrorMessage
+ }
+ return (
+ isLoading ||
+ !conditionalFieldItems ||
+ conditionalFieldItems.some(
+ ({ value: fieldValue }) => fieldValue === selectedValue,
+ ) ||
+ 'Field is not an dropdown field'
+ )
+ },
+ }}
+ render={({ field: { value = '', ...rest } }) => (
+
+ )}
+ />
+ {isSelectedConditionalFieldFound ? (
+ isOptionsToRecipientsMapAttached ? (
+ {}}
+ value={csvFile}
+ showDownload
+ showRemove
+ handleDownloadFileOverride={handleCsvDownload}
+ handleRemoveFileOverride={() =>
+ setIsDeleteConfirmModalOpen(true)
+ }
+ accept={['.csv']}
+ />
+ ) : (
+ }
+ onClick={onOpen}
+ isDisabled={!isSelectedConditionalFieldFound}
+ >
+ Add emails to options
+
+ )
+ ) : null}
+
+
+ {validateOptionsToRecipientsMapErrorMessage ||
+ errors.conditional_field?.message}
+
+
+ ) : null}
+
+ >
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.stories.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.stories.tsx
new file mode 100644
index 0000000000..f6da2ba005
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.stories.tsx
@@ -0,0 +1,162 @@
+import { useForm } from 'react-hook-form'
+import { expect, screen, userEvent, waitFor } from '@storybook/test'
+
+import { StoryRouter } from '~utils/storybook'
+
+import { ConditionalRoutingConfig } from './ConditionalRoutingOption'
+import {
+ ConditionalRoutingOptionModal,
+ ConditionalRoutingOptionModalProps,
+} from './ConditionalRoutingOptionModal'
+
+const ModalContainer = ({
+ isOpen,
+ onClose,
+ errors,
+ onDownloadCsvClick,
+ onSubmit,
+ isSubmitDisabled,
+ validateCsvFile,
+ csvFile,
+}: Omit & {
+ csvFile?: File | null
+}) => {
+ const { control } = useForm({
+ defaultValues: {
+ csvFile,
+ },
+ })
+
+ return (
+
+ )
+}
+
+export default {
+ component: ModalContainer,
+ title:
+ 'Features/AdminForm/create/workflow/components/WorkflowContent/EditStepBlock/ConditionalRoutingOptionModal',
+ args: {
+ isOpen: true,
+ onClose: () => {},
+ errors: {},
+ onDownloadCsvClick: () => {},
+ onSubmit: () => {},
+ isSubmitDisabled: false,
+ validateCsvFile: async () => undefined,
+ },
+ decorators: [StoryRouter({ initialEntries: ['/12345'], path: '/:formId' })],
+}
+
+export const DownloadCsvTemplateStep = {}
+
+export const UploadCsvFileStep = {
+ play: async () => {
+ const downloadButton = screen.getByText('Download and edit CSV template')
+ const nextButton = screen.getByText('Next: Upload CSV file')
+ await waitFor(
+ async () => {
+ expect(downloadButton).not.toBeDisabled()
+ await userEvent.click(downloadButton)
+ },
+ { timeout: 5000 },
+ )
+ await waitFor(
+ async () => {
+ expect(nextButton).not.toBeDisabled()
+ await userEvent.click(nextButton)
+ },
+ { timeout: 5000 },
+ )
+ await waitFor(
+ async () => {
+ expect(
+ screen.getByText('Upload your completed CSV file'),
+ ).toBeInTheDocument()
+ },
+ { timeout: 5000 },
+ )
+ },
+}
+
+export const UploadCsvFileStepWithAttachmentSelected = {
+ play: async () => {
+ const downloadButton = screen.getByText('Download and edit CSV template')
+ const nextButton = screen.getByText('Next: Upload CSV file')
+ await waitFor(
+ async () => {
+ expect(downloadButton).not.toBeDisabled()
+ await userEvent.click(downloadButton)
+ },
+ { timeout: 5000 },
+ )
+ await waitFor(
+ async () => {
+ expect(nextButton).not.toBeDisabled()
+ await userEvent.click(nextButton)
+ },
+ { timeout: 5000 },
+ )
+
+ await waitFor(
+ async () => {
+ expect(
+ screen.getByText('Upload your completed CSV file'),
+ ).toBeInTheDocument()
+ },
+ { timeout: 5000 },
+ )
+ },
+ args: {
+ csvFile: new File([''], 'test.csv', { type: 'text/csv' }),
+ },
+}
+
+export const UploadCsvFileStepWithAttachmentSelectedDummyErrorMessage = {
+ play: async () => {
+ const downloadButton = screen.getByText('Download and edit CSV template')
+ const nextButton = screen.getByText('Next: Upload CSV file')
+ await waitFor(
+ async () => {
+ expect(downloadButton).not.toBeDisabled()
+ await userEvent.click(downloadButton)
+ },
+ { timeout: 5000 },
+ )
+ await waitFor(
+ async () => {
+ expect(nextButton).not.toBeDisabled()
+ await userEvent.click(nextButton)
+ },
+ { timeout: 5000 },
+ )
+
+ await waitFor(
+ async () => {
+ expect(
+ screen.getByText('Upload your completed CSV file'),
+ ).toBeInTheDocument()
+ },
+ { timeout: 5000 },
+ )
+ },
+ args: {
+ csvFile: new File([''], 'test.csv', { type: 'text/csv' }),
+ errors: {
+ csvFile: {
+ message: 'Dummy error message',
+ },
+ },
+ },
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.tsx
new file mode 100644
index 0000000000..cf730ab720
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/ConditionalRoutingOptionModal.tsx
@@ -0,0 +1,278 @@
+import { useState } from 'react'
+import { Controller, FieldErrors, UseFormReturn } from 'react-hook-form'
+import { BiDownload } from 'react-icons/bi'
+import {
+ Box,
+ Button,
+ FormControl,
+ Image,
+ Modal,
+ ModalBody,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+ Stack,
+ Text,
+} from '@chakra-ui/react'
+
+import { MAX_UPLOAD_FILE_SIZE } from '~shared/constants'
+
+import { useIsMobile } from '~hooks/useIsMobile'
+import { NextAndBackButtonGroup } from '~components/Button'
+import Attachment from '~components/Field/Attachment'
+import FormErrorMessage from '~components/FormControl/FormErrorMessage'
+import { ModalCloseButton } from '~components/Modal'
+import { ProgressIndicator } from '~components/ProgressIndicator/ProgressIndicator'
+
+import CSV_TEMPLATE_EXAMPLE_IMAGE from './conditional-routing-example.png'
+import { ConditionalRoutingConfig } from './ConditionalRoutingOption'
+import { FieldItem } from './types'
+
+const NUM_STEPS = 2
+
+interface StepOneModalContentProps {
+ stepNumber: number
+ setStepNumber: (step: number) => void
+ isMobile: boolean
+ onDownloadCsvClick: ConditionalRoutingOptionModalProps['onDownloadCsvClick']
+ isCsvTemplateDownloaded: boolean
+ onClose: ConditionalRoutingOptionModalProps['onClose']
+}
+
+const StepOneModalContent = ({
+ stepNumber,
+ setStepNumber,
+ isMobile,
+ onDownloadCsvClick,
+ isCsvTemplateDownloaded,
+ onClose,
+}: StepOneModalContentProps) => {
+ return (
+
+
+
+ Add emails to options
+ {
+ if (selectedStepNumber > stepNumber && !isCsvTemplateDownloaded) {
+ return
+ }
+ setStepNumber(selectedStepNumber)
+ }}
+ />
+
+
+
+
+
+
+ We have created a CSV template with the options from the field
+ you selected.{' '}
+
+ Please download the CSV template and add the emails for each
+ option.
+
+
+ }
+ onClick={onDownloadCsvClick}
+ >
+ Download and edit CSV template
+
+
+
+
+ How to use the CSV template:
+
+
+
+ Column A
+
+
+ This contains all the options from your field.{' '}
+
+ Do not edit, reorder or delete anything in this column.
+
+
+
+
+
+ Column B
+
+
+ Add the emails to send the form to for each option.{' '}
+
+ Separate multiple email(s) with a comma.
+
+
+
+
+
+
+
+
+ Your CSV template should look like this
+
+
+
+
+
+ setStepNumber(1)}
+ isNextDisabled={!isCsvTemplateDownloaded}
+ />
+
+
+ )
+}
+
+interface StepTwoModalContentProps {
+ stepNumber: number
+ setStepNumber: (step: number) => void
+ control: ConditionalRoutingOptionModalProps['control']
+ errors: ConditionalRoutingOptionModalProps['errors']
+ onSubmit: ConditionalRoutingOptionModalProps['onSubmit']
+ isSubmitDisabled: ConditionalRoutingOptionModalProps['isSubmitDisabled']
+ validateCsvFile: ConditionalRoutingOptionModalProps['validateCsvFile']
+}
+
+const StepTwoModalContent = ({
+ stepNumber,
+ setStepNumber,
+ control,
+ errors,
+ onSubmit,
+ isSubmitDisabled,
+ validateCsvFile,
+}: StepTwoModalContentProps) => (
+
+
+
+ Upload your completed CSV file
+
+
+
+
+ Please ensure that your file is saved in{' '}
+
+ comma-separated values (.csv)
+ {' '}
+ format.
+
+
+ (
+
+ )}
+ />
+ {errors.csvFile?.message}
+
+
+
+ setStepNumber(0)}
+ handleNext={onSubmit}
+ isNextDisabled={isSubmitDisabled}
+ />
+
+
+)
+
+export interface ConditionalRoutingOptionModalProps {
+ isOpen: boolean
+ onClose: () => void
+ conditionalFieldItems: FieldItem[]
+ isLoading: boolean
+ control: UseFormReturn['control']
+ errors: FieldErrors
+ onDownloadCsvClick: () => void
+ onSubmit: () => void
+ isSubmitDisabled: boolean
+ validateCsvFile: (value: File | null) => Promise
+}
+
+export const ConditionalRoutingOptionModal = ({
+ isOpen,
+ onClose,
+ control,
+ errors,
+ onDownloadCsvClick,
+ onSubmit,
+ isSubmitDisabled,
+ validateCsvFile,
+}: ConditionalRoutingOptionModalProps): JSX.Element => {
+ const isMobile = useIsMobile()
+
+ const [stepNumber, setStepNumber] = useState(0)
+ const [isCsvTemplateDownloaded, setIsCsvTemplateDownloaded] = useState(false)
+
+ const onModalClose = () => {
+ setStepNumber(0)
+ onClose()
+ }
+
+ return (
+
+
+ {stepNumber === 0 && (
+ {
+ onDownloadCsvClick()
+ setIsCsvTemplateDownloaded(true)
+ }}
+ isCsvTemplateDownloaded={isCsvTemplateDownloaded}
+ onClose={onModalClose}
+ />
+ )}
+ {stepNumber === 1 && (
+
+ )}
+
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/DynamicRespondentOption.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/DynamicRespondentOption.tsx
new file mode 100644
index 0000000000..c562f3e620
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/DynamicRespondentOption.tsx
@@ -0,0 +1,86 @@
+import { Controller } from 'react-hook-form'
+import { FormControl, Text } from '@chakra-ui/react'
+
+import { WorkflowType } from '~shared/types'
+
+import { SingleSelect } from '~components/Dropdown'
+import FormErrorMessage from '~components/FormControl/FormErrorMessage'
+import Radio from '~components/Radio'
+
+import { WORKFLOW_TYPE_VALIDATION } from './common'
+import { FieldItem, RespondentOptionProps } from './types'
+
+interface DynamicRespondentOptionProps extends RespondentOptionProps {
+ emailFieldItems: FieldItem[]
+}
+
+export const DynamicRespondentOption = ({
+ isLoading,
+ selectedWorkflowType,
+ formMethods,
+ emailFieldItems,
+}: DynamicRespondentOptionProps) => {
+ const {
+ register,
+ formState: { errors },
+ control,
+ } = formMethods
+
+ return (
+ <>
+
+ An email field from the form
+ {selectedWorkflowType === WorkflowType.Dynamic ? (
+
+ {
+ return (
+ isLoading ||
+ !emailFieldItems ||
+ emailFieldItems.some(
+ ({ value: fieldValue }) => fieldValue === selectedValue,
+ ) ||
+ 'Field is not an email field'
+ )
+ },
+ }}
+ render={({ field: { value = '', ...rest } }) => (
+
+ )}
+ />
+ {errors.field?.message}
+
+ ) : null}
+
+ >
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx
new file mode 100644
index 0000000000..6afdfde9a2
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/StaticRespondentOption.tsx
@@ -0,0 +1,87 @@
+import { Controller } from 'react-hook-form'
+import { FormControl, Text } from '@chakra-ui/react'
+import { get } from 'lodash'
+import isEmail from 'validator/lib/isEmail'
+
+import { WorkflowType } from '~shared/types'
+
+import FormErrorMessage from '~components/FormControl/FormErrorMessage'
+import Radio from '~components/Radio'
+import { TagInput } from '~components/TagInput'
+
+import { WORKFLOW_TYPE_VALIDATION } from './common'
+import { RespondentOptionProps } from './types'
+
+export const StaticRespondentOption = ({
+ isLoading,
+ formMethods,
+ selectedWorkflowType,
+}: RespondentOptionProps) => {
+ const {
+ register,
+ control,
+ formState: { errors },
+ } = formMethods
+ const staticTagInputErrorMessage = get(errors, 'emails.message')
+
+ return (
+ <>
+
+ Specific email(s)
+ {selectedWorkflowType === WorkflowType.Static ? (
+
+
+ !emails || emails.length === 0
+ ? 'You must enter at least one email to receive responses'
+ : true,
+ isEmails: (emails) =>
+ !emails ||
+ emails.every((email) => isEmail(email)) ||
+ 'Please enter valid email(s) (e.g. me@example.com) separated by commas, as invalid emails will not be saved',
+ },
+ }}
+ render={({ field }) => (
+
+ )}
+ />
+ {staticTagInputErrorMessage}
+ {!staticTagInputErrorMessage ? (
+
+ Separate multiple emails with a comma
+
+ ) : null}
+
+ ) : null}
+
+ >
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/common.ts b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/common.ts
new file mode 100644
index 0000000000..6b7fc79225
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/common.ts
@@ -0,0 +1,10 @@
+import { WorkflowType } from '~shared/types/form/workflow'
+
+export const WORKFLOW_TYPE_VALIDATION = {
+ required: 'Please select a respondent type',
+ validate: (value: WorkflowType) => {
+ if (!Object.values(WorkflowType).includes(value)) {
+ return 'The selected respondent type is invalid'
+ }
+ },
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/conditional-routing-example.png b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/conditional-routing-example.png
new file mode 100644
index 0000000000..2fe884cc8b
Binary files /dev/null and b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/conditional-routing-example.png differ
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/types.ts b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/types.ts
new file mode 100644
index 0000000000..8a2fb480de
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/Components/types.ts
@@ -0,0 +1,18 @@
+import { UseFormReturn } from 'react-hook-form'
+import { As } from '@chakra-ui/react'
+
+import { WorkflowType } from '~shared/types/form/workflow'
+
+import { EditStepInputs } from '~features/admin-form/create/workflow/types'
+
+export interface RespondentOptionProps {
+ isLoading: boolean
+ formMethods: UseFormReturn
+ selectedWorkflowType: WorkflowType
+}
+
+export interface FieldItem {
+ label: string
+ value: string
+ icon?: As
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/RespondentBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/RespondentBlock.tsx
new file mode 100644
index 0000000000..ee1b32cb4c
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/RespondentBlock.tsx
@@ -0,0 +1,105 @@
+import { UseFormReturn } from 'react-hook-form'
+import { FormControl, Stack, Text } from '@chakra-ui/react'
+
+import { UserDto } from '~shared/types'
+
+import { textStyles } from '~theme/textStyles'
+import FormErrorMessage from '~components/FormControl/FormErrorMessage'
+import FormLabel from '~components/FormControl/FormLabel'
+import Radio from '~components/Radio'
+
+import { BASICFIELD_TO_DRAWER_META } from '~features/admin-form/create/constants'
+import { EditStepInputs } from '~features/admin-form/create/workflow/types'
+import { useUser } from '~features/user/queries'
+
+import { useAdminFormWorkflow } from '../../../../hooks/useAdminFormWorkflow'
+import { isFirstStepByStepNumber } from '../../utils/isFirstStepByStepNumber'
+import { EditStepBlockContainer } from '../EditStepBlockContainer'
+
+import { ConditionalRoutingOption } from './Components/ConditionalRoutingOption'
+import { DynamicRespondentOption } from './Components/DynamicRespondentOption'
+import { StaticRespondentOption } from './Components/StaticRespondentOption'
+
+interface RespondentBlockProps {
+ stepNumber: number
+ isLoading: boolean
+ formMethods: UseFormReturn
+ user: UserDto | undefined
+}
+
+export const RespondentBlock = ({
+ stepNumber,
+ isLoading,
+ formMethods,
+}: RespondentBlockProps): JSX.Element => {
+ const {
+ formState: { errors },
+ watch,
+ } = formMethods
+
+ // TODO (MRF-Conditional-Routing): Remove isTest and user/useUser when conditional routing is out of beta
+ const { user } = useUser()
+ const isTest = import.meta.env.STORYBOOK_NODE_ENV === 'test'
+
+ const { emailFormFields = [], dropdownFormFields = [] } =
+ useAdminFormWorkflow()
+
+ const emailFieldItems = emailFormFields.map(
+ ({ _id, questionNumber, title, fieldType }) => ({
+ label: `${questionNumber}. ${title}`,
+ value: _id,
+ icon: BASICFIELD_TO_DRAWER_META[fieldType].icon,
+ }),
+ )
+
+ const selectedWorkflowType = watch('workflow_type')
+
+ const isFirstStep = isFirstStepByStepNumber(stepNumber)
+
+ return (
+
+ {isFirstStep ? (
+
+ Respondent in this step
+ Anyone who has access to your form
+
+ ) : (
+
+ Select a respondent
+
+
+
+
+ {/* TODO (MRF-Conditional-Routing): Remove isTest and user check when
+ conditional routing is out of beta */}
+ {isTest || user?.betaFlags?.mrfConditionalRouting ? (
+ <>
+
+ >
+ ) : null}
+
+
+ {errors.workflow_type?.message}
+
+ )}
+
+ )
+}
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/index.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/index.tsx
new file mode 100644
index 0000000000..5edd75c0f1
--- /dev/null
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/RespondentBlock/index.tsx
@@ -0,0 +1 @@
+export { RespondentBlock } from './RespondentBlock'
diff --git a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
index 15b7419093..850b24f257 100644
--- a/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
+++ b/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx
@@ -3,8 +3,9 @@ import { BiPencil } from 'react-icons/bi'
import { Box, chakra, Flex, Stack, Text } from '@chakra-ui/react'
import { Dictionary } from 'lodash'
-import { FormField } from '~shared/types'
+import { BasicField, FormField } from '~shared/types'
import { FormWorkflowStepDto, WorkflowType } from '~shared/types/form'
+import { checkIsOptionsMismatched } from '~shared/utils/options-recipients-map-validation'
import IconButton from '~components/IconButton'
@@ -48,6 +49,43 @@ const SubsequentStepRespondentBadges = ({
)
case WorkflowType.Dynamic:
return
+ case WorkflowType.Conditional: {
+ const selectedConditionalField = idToFieldMap[step.conditional_field]
+ if (
+ !selectedConditionalField ||
+ selectedConditionalField.fieldType !== BasicField.Dropdown
+ ) {
+ return
+ }
+ const selectedConditionalFieldOptions =
+ selectedConditionalField.fieldOptions
+ const optionsToRecipientsMapOptions = Object.keys(
+ selectedConditionalField.optionsToRecipientsMap || {},
+ )
+ const isOptionsMismatched = checkIsOptionsMismatched(
+ optionsToRecipientsMapOptions,
+ selectedConditionalFieldOptions,
+ )
+ return (
+
+
+ {isOptionsMismatched ? (
+
+ ) : null}
+
+ )
+ }
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = step
@@ -108,8 +146,7 @@ export const InactiveStepBlock = ({
field={idToFieldMap[fieldId]}
defaults={{
variant: 'info',
- message:
- 'This field was deleted and has been removed from your workflow',
+ message: 'This field was deleted, please select another field',
}}
/>
))
diff --git a/frontend/src/features/admin-form/create/workflow/hooks/useAdminFormWorkflow.ts b/frontend/src/features/admin-form/create/workflow/hooks/useAdminFormWorkflow.ts
index 3434f1e13f..b8bbc7ded2 100644
--- a/frontend/src/features/admin-form/create/workflow/hooks/useAdminFormWorkflow.ts
+++ b/frontend/src/features/admin-form/create/workflow/hooks/useAdminFormWorkflow.ts
@@ -3,6 +3,7 @@ import { keyBy } from 'lodash'
import {
BasicField,
+ DropdownFieldBase,
EmailFieldBase,
FormFieldDto,
FormResponseMode,
@@ -48,6 +49,17 @@ export const useAdminFormWorkflow = () => {
[augmentedFormFields],
)
+ const dropdownFormFields = useMemo(
+ () =>
+ augmentedFormFields.filter(
+ (
+ field,
+ ): field is FormFieldWithQuestionNo> =>
+ field.fieldType === BasicField.Dropdown,
+ ),
+ [augmentedFormFields],
+ )
+
const formWorkflow =
form?.responseMode === FormResponseMode.Multirespondent
? form.workflow
@@ -60,5 +72,6 @@ export const useAdminFormWorkflow = () => {
idToFieldMap,
emailFormFields,
yesNoFormFields,
+ dropdownFormFields,
}
}
diff --git a/frontend/src/features/admin-form/create/workflow/mutations.ts b/frontend/src/features/admin-form/create/workflow/mutations.ts
index 06e20ae440..86153f605b 100644
--- a/frontend/src/features/admin-form/create/workflow/mutations.ts
+++ b/frontend/src/features/admin-form/create/workflow/mutations.ts
@@ -114,5 +114,9 @@ export const useWorkflowMutations = () => {
},
)
- return { createStepMutation, deleteStepMutation, updateStepMutation }
+ return {
+ createStepMutation,
+ deleteStepMutation,
+ updateStepMutation,
+ }
}
diff --git a/frontend/src/features/admin-form/create/workflow/types.ts b/frontend/src/features/admin-form/create/workflow/types.ts
index 68e2eba413..4998bb1e19 100644
--- a/frontend/src/features/admin-form/create/workflow/types.ts
+++ b/frontend/src/features/admin-form/create/workflow/types.ts
@@ -11,9 +11,11 @@ export enum AdminEditWorkflowState {
EditingStep,
}
-export type EditStepInputs = Omit & {
+export type EditStepInputs = FormWorkflowStep & {
+ _id: string
workflow_type: WorkflowType
emails?: FormWorkflowStepStatic['emails']
field?: FormWorkflowStepDynamic['field']
approval_field?: FormFieldDto['_id']
+ conditional_field?: FormFieldDto['_id']
}
diff --git a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/FormWhitelistAttachmentField.tsx b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/FormWhitelistAttachmentField.tsx
index 7a9ec94bc2..edaf41c44a 100644
--- a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/FormWhitelistAttachmentField.tsx
+++ b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/FormWhitelistAttachmentField.tsx
@@ -109,7 +109,7 @@ export const FormWhitelistAttachmentField = ({
headerRow[0].replace(/(\r\n|\n|\r)/gm, '').toLowerCase() ===
'respondent',
invalidReason:
- 'Your CSV file should only contain a single column with the "Respondent" header.',
+ 'Your CSV file should only contain a single column with the header "Respondent".',
}
})
diff --git a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/SecretKeyDownloadWhitelistFileModal.tsx b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/SecretKeyDownloadWhitelistFileModal.tsx
index 344f0c9e0c..d5d54bd75a 100644
--- a/frontend/src/features/admin-form/settings/components/AuthSettingsSection/SecretKeyDownloadWhitelistFileModal.tsx
+++ b/frontend/src/features/admin-form/settings/components/AuthSettingsSection/SecretKeyDownloadWhitelistFileModal.tsx
@@ -130,7 +130,7 @@ export const SecretKeyDownloadWhitelistFileModal = ({
onClose={onClose}
isOpen={isOpen}
publicKey={publicKey}
- modalActionText=" Download CSV file of whitelisted NRIC/FIN/UEN(s)"
+ modalActionText="Download CSV file of whitelisted NRIC/FIN/UEN(s)"
submitButtonText="Download file"
hasAck={false}
/>
diff --git a/frontend/src/features/admin-form/settings/components/GeneralTabHeader.tsx b/frontend/src/features/admin-form/settings/components/GeneralTabHeader.tsx
index 08bba58986..f07ee84f97 100644
--- a/frontend/src/features/admin-form/settings/components/GeneralTabHeader.tsx
+++ b/frontend/src/features/admin-form/settings/components/GeneralTabHeader.tsx
@@ -1,20 +1,20 @@
+import { useTranslation } from 'react-i18next'
import { Skeleton, Wrap } from '@chakra-ui/react'
import Badge from '~components/Badge'
-import { RESPONSE_MODE_TO_TEXT } from '~features/admin-form/common/constants'
-
import { useAdminFormSettings } from '../queries'
import { CategoryHeader } from './CategoryHeader'
export const GeneralTabHeader = (): JSX.Element => {
+ const { t } = useTranslation()
const { data: settings, isLoading: isLoadingSettings } =
useAdminFormSettings()
const readableFormResponseMode = !settings
? 'Loading...'
- : RESPONSE_MODE_TO_TEXT[settings.responseMode]
+ : t(`features.adminForm.meta.responseModeText.${settings.responseMode}`)
return (
{
+ const canvas = within(canvasElement)
+ await waitFor(async () => {
+ await expect(canvas.getByText(/yes\/no/i)).toBeInTheDocument
+ })
+ await waitFor(async () => {
+ const noQuestionChoice = canvas.getByRole('button', {
+ name: /1\. yes\/no no option, unselected/i,
+ })
+ await userEvent.click(noQuestionChoice)
+ })
+ await waitFor(
+ async () => {
+ await userEvent.click(canvas.getByRole('button', { name: /submit/i }))
+ },
+ {
+ timeout: 5000,
+ },
+ )
+ await waitFor(
+ async () => {
+ await expect(
+ canvas.getByRole('link', { name: /submit another form/i }),
+ ).toBeInTheDocument()
+ },
+ {
+ timeout: 5000,
+ },
+ )
+}
diff --git a/frontend/src/features/public-form/PublicFormProvider.tsx b/frontend/src/features/public-form/PublicFormProvider.tsx
index 53714f67af..79bd1f6b77 100644
--- a/frontend/src/features/public-form/PublicFormProvider.tsx
+++ b/frontend/src/features/public-form/PublicFormProvider.tsx
@@ -14,6 +14,7 @@ import { useDisclosure } from '@chakra-ui/react'
import { datadogLogs } from '@datadog/browser-logs'
import { useGrowthBook } from '@growthbook/growthbook-react'
import { differenceInMilliseconds, isPast } from 'date-fns'
+import { flow } from 'lodash'
import get from 'lodash/get'
import {
@@ -123,6 +124,63 @@ export function useCommonFormProvider(formId: string) {
}
}
+// Country/region data must be upper-case in backend for myinfo-countries compatibility,
+// while displaying in title-case to users in the frontend.
+// Hence, we need to map the frontend title-case to upper-case when submitting to backend.
+const transformFormInputCountryRegionToUpperCase =
+ (form_fields: Array<{ fieldType: BasicField; _id: string }>) =>
+ (formInputs: Record) => {
+ const countryRegionFieldIds = new Set(
+ form_fields
+ .filter((field) => field.fieldType === BasicField.CountryRegion)
+ .map((field) => field._id),
+ )
+
+ return Object.keys(formInputs).reduce(
+ (newFormInputs: typeof formInputs, fieldId) => {
+ const currentInput = formInputs[fieldId]
+ if (
+ countryRegionFieldIds.has(fieldId) &&
+ typeof currentInput === 'string'
+ ) {
+ newFormInputs[fieldId] = currentInput.toUpperCase()
+ } else {
+ newFormInputs[fieldId] = currentInput
+ }
+ return newFormInputs
+ },
+ {},
+ )
+ }
+
+// Trim text inputs before sending to backend to match frontend validation
+const transformFormInputTrimTextInputs =
+ (form_fields: Array<{ fieldType: BasicField; _id: string }>) =>
+ (formInputs: Record) => {
+ const textFieldIds = new Set(
+ form_fields
+ .filter(
+ (field) =>
+ field.fieldType === BasicField.ShortText ||
+ field.fieldType === BasicField.LongText,
+ )
+ .map((field) => field._id),
+ )
+
+ return Object.keys(formInputs).reduce(
+ (newFormInputs: typeof formInputs, fieldId) => {
+ const currentInput = formInputs[fieldId]
+ if (textFieldIds.has(fieldId) && typeof currentInput === 'string') {
+ newFormInputs[fieldId] = currentInput.trim()
+ } else {
+ newFormInputs[fieldId] = currentInput
+ }
+ return newFormInputs
+ },
+ {},
+ )
+ }
+
export const PublicFormProvider = ({
formId,
submissionId: previousSubmissionId,
@@ -515,32 +573,15 @@ export const PublicFormProvider = ({
}
}
- const countryRegionFieldIds = new Set(
- form.form_fields
- .filter((field) => field.fieldType === BasicField.CountryRegion)
- .map((field) => field._id),
- )
- // We want users to see the country/region options in title-case but we also need the data in the backend to remain in upper-case.
- // Country/region data in the backend needs to remain in upper-case so that they remain consistent with myinfo-countries.
- const formInputsWithCountryRegionInUpperCase = Object.keys(
- formInputs,
- ).reduce((newFormInputs: typeof formInputs, fieldId) => {
- const currentInput = formInputs[fieldId]
- if (
- countryRegionFieldIds.has(fieldId) &&
- typeof currentInput === 'string'
- ) {
- newFormInputs[fieldId] = currentInput.toUpperCase()
- } else {
- newFormInputs[fieldId] = currentInput
- }
- return newFormInputs
- }, {})
+ const transformedFormInputs = flow([
+ transformFormInputCountryRegionToUpperCase(form.form_fields),
+ transformFormInputTrimTextInputs(form.form_fields),
+ ])(formInputs) as typeof formInputs
const formData = {
formFields: form.form_fields,
formLogics: form.form_logics,
- formInputs: formInputsWithCountryRegionInUpperCase,
+ formInputs: transformedFormInputs,
captchaResponse,
captchaType,
responseMetadata: {
@@ -602,7 +643,7 @@ export const PublicFormProvider = ({
.mutateAsync(
{
...formData,
- formInputs: formInputsWithCountryRegionInUpperCase,
+ formInputs: transformedFormInputs,
},
{ onSuccess },
)
@@ -640,7 +681,7 @@ export const PublicFormProvider = ({
.mutateAsync(
{
...formData,
- formInputs: formInputsWithCountryRegionInUpperCase,
+ formInputs: transformedFormInputs,
},
{ onSuccess },
)
diff --git a/frontend/src/features/workspace/components/WorkspaceFormRow/WorkspaceFormRow.tsx b/frontend/src/features/workspace/components/WorkspaceFormRow/WorkspaceFormRow.tsx
index c3ebfb220e..69c830ecb0 100644
--- a/frontend/src/features/workspace/components/WorkspaceFormRow/WorkspaceFormRow.tsx
+++ b/frontend/src/features/workspace/components/WorkspaceFormRow/WorkspaceFormRow.tsx
@@ -1,4 +1,5 @@
import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
import { Link as ReactLink } from 'react-router-dom'
import { Box, ButtonProps, chakra, Flex, Text } from '@chakra-ui/react'
import dayjs from 'dayjs'
@@ -8,8 +9,6 @@ import { AdminDashboardFormMetaDto, FormStatus } from '~shared/types/form/form'
import { ADMINFORM_ROUTE } from '~constants/routes'
import Badge from '~components/Badge'
-import { RESPONSE_MODE_TO_TEXT } from '~features/admin-form/common/constants'
-
import { FormStatusLabel } from './FormStatusLabel'
import { RowActions } from './RowActions'
@@ -17,22 +16,17 @@ export interface WorkspaceFormRowProps extends ButtonProps {
formMeta: AdminDashboardFormMetaDto
}
-const RELATIVE_DATE_FORMAT = {
- sameDay: '[today,] D MMM h:mma', // today, 16 Jun 9:30am
- nextDay: '[tomorrow,] D MMM h:mma', // tomorrow, 16 Jun 9:30am
- lastDay: '[yesterday,] D MMM h:mma', // yesterday, 16 Jun 9:30am
- nextWeek: 'ddd, D MMM YYYY h:mma', // Tue, 17 Oct 2021 9:30pm
- lastWeek: 'ddd, D MMM YYYY h:mma', // Tue, 17 Oct 2021 9:30pm
- sameElse: 'D MMM YYYY h:mma', // 6 Oct 2021 9:30pm
-}
-
export const WorkspaceFormRow = ({
formMeta,
...buttonProps
}: WorkspaceFormRowProps): JSX.Element => {
+ const { t } = useTranslation()
+ const relativeDateFormat = t('features.adminForm.meta.relativeDateFormat', {
+ returnObjects: true,
+ })
const prettyLastModified = useMemo(() => {
- return dayjs(formMeta.lastModified).calendar(null, RELATIVE_DATE_FORMAT)
- }, [formMeta.lastModified])
+ return dayjs(formMeta.lastModified).calendar(null, relativeDateFormat)
+ }, [formMeta.lastModified, relativeDateFormat])
return (
@@ -83,12 +77,19 @@ export const WorkspaceFormRow = ({
{formMeta.title}
- Edited {prettyLastModified}
+ {t('features.adminForm.meta.prettyLastModified', {
+ prettyLastModified,
+ })}
- {RESPONSE_MODE_TO_TEXT[formMeta.responseMode]}
+ {t(
+ `features.adminForm.meta.responseModeText.${formMeta.responseMode}`,
+ {
+ prettyLastModified,
+ },
+ )}
diff --git a/frontend/src/i18n/locales/features/admin-form/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/en-sg.ts
index 0a4c022da9..b194d9d51e 100644
--- a/frontend/src/i18n/locales/features/admin-form/en-sg.ts
+++ b/frontend/src/i18n/locales/features/admin-form/en-sg.ts
@@ -1,7 +1,13 @@
+import { enSG as meta } from './meta'
+import { enSG as modals } from './modals'
import { enSG as navbar } from './navbar'
import { enSG as sidebar } from './sidebar'
+import { enSG as toasts } from './toasts'
export const enSG = {
navbar,
sidebar,
+ meta,
+ modals,
+ toasts,
}
diff --git a/frontend/src/i18n/locales/features/admin-form/index.ts b/frontend/src/i18n/locales/features/admin-form/index.ts
index 11ebfdc6ac..34644c6f72 100644
--- a/frontend/src/i18n/locales/features/admin-form/index.ts
+++ b/frontend/src/i18n/locales/features/admin-form/index.ts
@@ -1,6 +1,9 @@
export * from './en-sg'
+export { type Meta } from './meta'
+export { type Modals } from './modals'
export { type Navbar } from './navbar'
export { type Fields } from './sidebar'
export { type HeaderAndInstructions } from './sidebar'
export { type Logic } from './sidebar'
export { type ThankYou } from './sidebar'
+export { type Toasts } from './toasts'
diff --git a/frontend/src/i18n/locales/features/admin-form/meta/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/meta/en-sg.ts
new file mode 100644
index 0000000000..ccba7a006e
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/meta/en-sg.ts
@@ -0,0 +1,18 @@
+import { FormResponseMode } from '~shared/types'
+
+export const enSG = {
+ prettyLastModified: 'Edited {prettyLastModified}',
+ relativeDateFormat: {
+ sameDay: '[today,] D MMM h:mma', // today, 16 Jun 9:30am
+ nextDay: '[tomorrow,] D MMM h:mma', // tomorrow, 16 Jun 9:30am
+ lastDay: '[yesterday,] D MMM h:mma', // yesterday, 16 Jun 9:30am
+ nextWeek: 'ddd, D MMM YYYY h:mma', // Tue, 17 Oct 2021 9:30pm
+ lastWeek: 'ddd, D MMM YYYY h:mma', // Tue, 17 Oct 2021 9:30pm
+ sameElse: 'D MMM YYYY h:mma', // 6 Oct 2021 9:30pm
+ },
+ responseModeText: {
+ [FormResponseMode.Multirespondent]: 'Multi-respondent form',
+ [FormResponseMode.Email]: 'Email mode',
+ [FormResponseMode.Encrypt]: 'Storage mode',
+ },
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/meta/index.ts b/frontend/src/i18n/locales/features/admin-form/meta/index.ts
new file mode 100644
index 0000000000..2888a1f1c5
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/meta/index.ts
@@ -0,0 +1,18 @@
+import { FormResponseMode } from '~shared/types'
+
+export * from './en-sg'
+
+export interface Meta {
+ prettyLastModified: string
+ relativeDateFormat: {
+ sameDay: string
+ nextDay: string
+ lastDay: string
+ nextWeek: string
+ lastWeek: string
+ sameElse: string
+ }
+ responseModeText: {
+ [k in FormResponseMode]: string
+ }
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/modals/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/modals/en-sg.ts
new file mode 100644
index 0000000000..0a86a083d4
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/modals/en-sg.ts
@@ -0,0 +1,23 @@
+export const enSG = {
+ deleteField: {
+ title: 'Delete field',
+ description: {
+ field:
+ 'Are you sure you want to delete this field? This action cannot be undone.',
+ logic:
+ 'This field is used in your form logic, so deleting it may cause your logic to stop working correctly. Are you sure you want to delete this field?',
+ payment:
+ "Are you sure you want to delete payment field? This action can't be undone.",
+ },
+ confirmButtonText: 'Yes, delete field',
+ },
+ unsavedChanges: {
+ title: 'You have unsaved changes',
+ description: 'Are you sure you want to leave? Your changes will be lost.',
+ confirmButtonText: 'Yes, discard changes',
+ cancelButtonText: 'No, stay on page',
+ },
+ dirty: {
+ cancelButtonText: 'No, return to editing',
+ },
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/modals/index.ts b/frontend/src/i18n/locales/features/admin-form/modals/index.ts
new file mode 100644
index 0000000000..27a681fbb8
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/modals/index.ts
@@ -0,0 +1,22 @@
+export * from './en-sg'
+
+export interface Modals {
+ deleteField: {
+ title: string
+ description: {
+ field: string
+ logic: string
+ payment: string
+ }
+ confirmButtonText: string
+ }
+ unsavedChanges: {
+ title: string
+ description: string
+ confirmButtonText: string
+ cancelButtonText: string
+ }
+ dirty: {
+ cancelButtonText: string
+ }
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/toasts/en-sg.ts b/frontend/src/i18n/locales/features/admin-form/toasts/en-sg.ts
new file mode 100644
index 0000000000..7bd6036ff7
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/toasts/en-sg.ts
@@ -0,0 +1,26 @@
+export const enSG = {
+ field: {
+ delete: {
+ success: 'The {field} was deleted.',
+ error:
+ 'Something went wrong when deleting your field. Please refresh and try again.',
+ },
+ create: {
+ success: 'The {field} was created.',
+ error:
+ 'Something went wrong when creating your field. Please refresh and try again.',
+ },
+ update: {
+ success: 'The {field} was updated.',
+ error:
+ 'Something went wrong when editing your field. Please refresh and try again.',
+ },
+ duplicate: {
+ success: 'The {field} was duplicated.',
+ successButNoLogic:
+ 'The {field} was duplicated. Associated logic was not duplicated.',
+ error:
+ 'Something went wrong when creating your field. Please refresh and try again.',
+ },
+ },
+}
diff --git a/frontend/src/i18n/locales/features/admin-form/toasts/index.ts b/frontend/src/i18n/locales/features/admin-form/toasts/index.ts
new file mode 100644
index 0000000000..d82358aa13
--- /dev/null
+++ b/frontend/src/i18n/locales/features/admin-form/toasts/index.ts
@@ -0,0 +1,15 @@
+export * from './en-sg'
+
+interface Toast {
+ success: string
+ error: string
+}
+
+export interface Toasts {
+ field: {
+ delete: Toast
+ create: Toast
+ update: Toast
+ duplicate: Toast & { successButNoLogic: string }
+ }
+}
diff --git a/frontend/src/i18n/locales/features/common/en-sg.ts b/frontend/src/i18n/locales/features/common/en-sg.ts
index 3942bae1a6..910803c2dd 100644
--- a/frontend/src/i18n/locales/features/common/en-sg.ts
+++ b/frontend/src/i18n/locales/features/common/en-sg.ts
@@ -55,6 +55,7 @@ export const enSG: Common = {
tooltip: {
deleteField: 'Delete field',
duplicateField: 'Duplicate field',
+ editField: 'Edit field',
},
dropdown: {
placeholder: 'Select an option',
diff --git a/frontend/src/i18n/locales/features/common/index.ts b/frontend/src/i18n/locales/features/common/index.ts
index bc84abddfa..304c1d78c2 100644
--- a/frontend/src/i18n/locales/features/common/index.ts
+++ b/frontend/src/i18n/locales/features/common/index.ts
@@ -55,6 +55,7 @@ export interface Common {
tooltip: {
deleteField: string
duplicateField: string
+ editField: string
}
dropdown: {
placeholder: string
diff --git a/frontend/src/i18n/locales/features/index.ts b/frontend/src/i18n/locales/features/index.ts
index 72f4eb1983..b612fc14f5 100644
--- a/frontend/src/i18n/locales/features/index.ts
+++ b/frontend/src/i18n/locales/features/index.ts
@@ -1,8 +1,13 @@
-export { type Navbar } from './admin-form'
-export { type Fields } from './admin-form'
-export { type HeaderAndInstructions } from './admin-form'
-export { type Logic } from './admin-form'
-export { type ThankYou } from './admin-form'
+export {
+ type Fields,
+ type HeaderAndInstructions,
+ type Logic,
+ type Meta,
+ type Modals,
+ type Navbar,
+ type ThankYou,
+ type Toasts,
+} from './admin-form'
export { type Common } from './common'
export { type Login } from './login'
export { type PublicForm } from './public-form'
diff --git a/frontend/src/i18n/locales/types.ts b/frontend/src/i18n/locales/types.ts
index 201e5f9cb7..9bcc910574 100644
--- a/frontend/src/i18n/locales/types.ts
+++ b/frontend/src/i18n/locales/types.ts
@@ -6,9 +6,12 @@ import {
HeaderAndInstructions,
Logic,
Login,
+ Meta,
+ Modals,
Navbar,
PublicForm,
ThankYou,
+ Toasts,
} from './features'
interface Translation {
@@ -22,6 +25,9 @@ interface Translation {
thankYou?: ThankYou
}
navbar?: Navbar
+ meta?: Meta
+ modals?: Modals
+ toasts?: Toasts
}
common?: Common
publicForm?: PublicForm
diff --git a/frontend/src/mocks/msw/handlers/public-form.ts b/frontend/src/mocks/msw/handlers/public-form.ts
index ccecba1e9f..f400a5d524 100644
--- a/frontend/src/mocks/msw/handlers/public-form.ts
+++ b/frontend/src/mocks/msw/handlers/public-form.ts
@@ -486,6 +486,24 @@ export const getPublicFormWithoutSectionsResponse = ({
)
}
+export const getPublicFormSubmissionSuccessResponse = (
+ type: 'email' | 'storage' = 'email',
+) => {
+ return rest.post(
+ `/api/v3/forms/:formId/submissions/${type}`,
+ (_req, res, ctx) => {
+ return res(
+ ctx.status(200),
+ ctx.json({
+ message: 'Form submission successful.',
+ submissionId: '6625dfd68f4364af26332097',
+ timestamp: 1713758166140,
+ }),
+ )
+ },
+ )
+}
+
export const getPublicFormErrorResponse = ({
delay = 0,
status = 404,
diff --git a/frontend/src/templates/NavigationPrompt/NavigationPrompt.tsx b/frontend/src/templates/NavigationPrompt/NavigationPrompt.tsx
index 9bd84ab36f..8860b847a6 100644
--- a/frontend/src/templates/NavigationPrompt/NavigationPrompt.tsx
+++ b/frontend/src/templates/NavigationPrompt/NavigationPrompt.tsx
@@ -1,4 +1,5 @@
import { memo } from 'react'
+import { useTranslation } from 'react-i18next'
import { UnsavedChangesModal } from './UnsavedChangesModal'
import { useNavigationPrompt } from './useNavigationPrompt'
@@ -26,8 +27,13 @@ export const NavigationPrompt = memo(
confirmButtonText = 'Yes, discard changes',
cancelButtonText = 'No, stay on page',
}: NavigationPromptProps) => {
+ const { t } = useTranslation()
const { isPromptShown, onCancel, onConfirm } = useNavigationPrompt(when)
+ const defaultText = t('features.adminForm.modals.unsavedChanges', {
+ returnObjects: true,
+ })
+
return (
=4.2.3",
"nan": "^2.19.0",
"neverthrow": "^8.0.0",
- "nocache": "^3.0.4",
"node-cache": "^5.1.2",
"nodemailer": "^6.9.16",
"openai": "^4.70.3",
@@ -114,7 +113,7 @@
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.25.4",
"@opengovsg/mockpass": "^4.3.4",
- "@playwright/test": "^1.45.1",
+ "@playwright/test": "^1.49.0",
"@stoplight/prism-cli": "^5.10.0",
"@types/bcrypt": "^5.0.0",
"@types/bluebird": "^3.5.42",
@@ -134,7 +133,6 @@
"@types/ip": "^1.1.0",
"@types/jest": "^29.5.1",
"@types/json-stringify-safe": "^5.0.3",
- "@types/jsonfile": "^6.1.1",
"@types/jsonwebtoken": "^9.0.7",
"@types/jwk-to-pem": "^2.0.3",
"@types/lodash": "^4.17.6",
@@ -179,7 +177,6 @@
"maildev": "^2.1.0",
"mockdate": "^3.0.5",
"prettier": "^3.3.3",
- "regenerator": "^0.14.10",
"rimraf": "^5.0.5",
"stripe-event-types": "^3.1.0",
"supertest": "^6.3.3",
@@ -190,8 +187,6 @@
"ts-node-dev": "^2.0.0",
"type-fest": "^4.17.0",
"typescript": "^5.4.5",
- "webpack": "^4.46.0",
- "webpack-cli": "^4.10.0",
"worker-loader": "^2.0.0"
},
"engines": {
@@ -3793,23 +3788,6 @@
"@babel/core": "^7.0.0"
}
},
- "node_modules/@babel/plugin-proposal-function-sent": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.18.6.tgz",
- "integrity": "sha512-UdaOKPOLPt0O+Xu26tnw6oAZMLXhk+yMrXOzn6kAzTHBnWHJsoN1hlrgxFAQ+FRLS0ql1oYIQ2phvoFzmN3GMw==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6",
- "@babel/helper-wrap-function": "^7.18.6",
- "@babel/plugin-syntax-function-sent": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
@@ -3896,21 +3874,6 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/plugin-syntax-function-sent": {
- "version": "7.18.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.18.6.tgz",
- "integrity": "sha512-f3OJHIlFIkg+cP1Hfo2SInLhsg0pz2Ikmgo7jMdIIKC+3jVXQlHB0bgSapOWxeWI0SU28qIWmfn5ZKu1yPJHkg==",
- "dev": true,
- "dependencies": {
- "@babel/helper-plugin-utils": "^7.18.6"
- },
- "engines": {
- "node": ">=6.9.0"
- },
- "peerDependencies": {
- "@babel/core": "^7.0.0-0"
- }
- },
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz",
@@ -5309,15 +5272,6 @@
"version": "2.1.0",
"license": "Apache-2.0"
},
- "node_modules/@discoveryjs/json-ext": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
- "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
- "dev": true,
- "engines": {
- "node": ">=10.0.0"
- }
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.19.11",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
@@ -7091,12 +7045,12 @@
}
},
"node_modules/@playwright/test": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
- "integrity": "sha512-Wo1bWTzQvGA7LyKGIZc8nFSTFf2TkthGIFBR+QVNilvwouGzFd4PYukZe3rvf5PSqjHi1+1NyKSDZKcQWETzaA==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz",
+ "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==",
"dev": true,
"dependencies": {
- "playwright": "1.45.1"
+ "playwright": "1.49.0"
},
"bin": {
"playwright": "cli.js"
@@ -9270,15 +9224,6 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
- "node_modules/@types/jsonfile": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz",
- "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@types/jsonwebtoken": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
@@ -10207,209 +10152,6 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
- "node_modules/@webassemblyjs/ast": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/helper-module-context": "1.9.0",
- "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
- "@webassemblyjs/wast-parser": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/floating-point-hex-parser": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-api-error": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-buffer": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-code-frame": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/wast-printer": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/helper-fsm": {
- "version": "1.9.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/@webassemblyjs/helper-module-context": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/helper-wasm-bytecode": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/helper-wasm-section": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-buffer": "1.9.0",
- "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
- "@webassemblyjs/wasm-gen": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/ieee754": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@xtuc/ieee754": "^1.2.0"
- }
- },
- "node_modules/@webassemblyjs/leb128": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/utf8": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@webassemblyjs/wasm-edit": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-buffer": "1.9.0",
- "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
- "@webassemblyjs/helper-wasm-section": "1.9.0",
- "@webassemblyjs/wasm-gen": "1.9.0",
- "@webassemblyjs/wasm-opt": "1.9.0",
- "@webassemblyjs/wasm-parser": "1.9.0",
- "@webassemblyjs/wast-printer": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/wasm-gen": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
- "@webassemblyjs/ieee754": "1.9.0",
- "@webassemblyjs/leb128": "1.9.0",
- "@webassemblyjs/utf8": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/wasm-opt": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-buffer": "1.9.0",
- "@webassemblyjs/wasm-gen": "1.9.0",
- "@webassemblyjs/wasm-parser": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/wasm-parser": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-api-error": "1.9.0",
- "@webassemblyjs/helper-wasm-bytecode": "1.9.0",
- "@webassemblyjs/ieee754": "1.9.0",
- "@webassemblyjs/leb128": "1.9.0",
- "@webassemblyjs/utf8": "1.9.0"
- }
- },
- "node_modules/@webassemblyjs/wast-parser": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/floating-point-hex-parser": "1.9.0",
- "@webassemblyjs/helper-api-error": "1.9.0",
- "@webassemblyjs/helper-code-frame": "1.9.0",
- "@webassemblyjs/helper-fsm": "1.9.0",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webassemblyjs/wast-printer": {
- "version": "1.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/wast-parser": "1.9.0",
- "@xtuc/long": "4.2.2"
- }
- },
- "node_modules/@webpack-cli/configtest": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
- "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
- "dev": true,
- "peerDependencies": {
- "webpack": "4.x.x || 5.x.x",
- "webpack-cli": "4.x.x"
- }
- },
- "node_modules/@webpack-cli/info": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
- "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
- "dev": true,
- "dependencies": {
- "envinfo": "^7.7.3"
- },
- "peerDependencies": {
- "webpack-cli": "4.x.x"
- }
- },
- "node_modules/@webpack-cli/serve": {
- "version": "1.7.0",
- "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
- "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
- "dev": true,
- "peerDependencies": {
- "webpack-cli": "4.x.x"
- },
- "peerDependenciesMeta": {
- "webpack-dev-server": {
- "optional": true
- }
- }
- },
- "node_modules/@xtuc/ieee754": {
- "version": "1.2.0",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/@xtuc/long": {
- "version": "4.2.2",
- "dev": true,
- "license": "Apache-2.0"
- },
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -10535,14 +10277,6 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/ajv-errors": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "ajv": ">=5.0.0"
- }
- },
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
@@ -10702,30 +10436,6 @@
"sprintf-js": "~1.0.2"
}
},
- "node_modules/arr-diff": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-flatten": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/arr-union": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array-buffer-byte-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
@@ -10775,14 +10485,6 @@
"node": ">=8"
}
},
- "node_modules/array-unique": {
- "version": "0.3.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/array.prototype.findlastindex": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz",
@@ -10883,52 +10585,6 @@
"safer-buffer": "^2.1.0"
}
},
- "node_modules/assert": {
- "version": "1.5.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4.1.1",
- "util": "0.10.3"
- }
- },
- "node_modules/assert/node_modules/inherits": {
- "version": "2.0.1",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/assert/node_modules/util": {
- "version": "0.10.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "2.0.1"
- }
- },
- "node_modules/assign-symbols": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ast-types": {
- "version": "0.15.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.1"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/ast-types/node_modules/tslib": {
- "version": "2.4.0",
- "dev": true,
- "license": "0BSD"
- },
"node_modules/astral-regex": {
"version": "2.0.0",
"dev": true,
@@ -10941,12 +10597,6 @@
"version": "3.2.3",
"license": "MIT"
},
- "node_modules/async-each": {
- "version": "1.0.3",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/async-mutex": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz",
@@ -10964,17 +10614,6 @@
"version": "0.4.0",
"license": "MIT"
},
- "node_modules/atob": {
- "version": "2.1.2",
- "dev": true,
- "license": "(MIT OR Apache-2.0)",
- "bin": {
- "atob": "bin/atob.js"
- },
- "engines": {
- "node": ">= 4.5.0"
- }
- },
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
@@ -11394,92 +11033,29 @@
"bare-os": "^2.1.0"
}
},
- "node_modules/base": {
- "version": "0.11.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cache-base": "^1.0.1",
- "class-utils": "^0.3.5",
- "component-emitter": "^1.2.1",
- "define-property": "^1.0.0",
- "isobject": "^3.0.1",
- "mixin-deep": "^1.2.0",
- "pascalcase": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/base-64": {
"version": "1.0.0",
"dev": true,
"license": "MIT"
},
- "node_modules/base/node_modules/define-property": {
- "version": "1.0.0",
+ "node_modules/base32.js": {
+ "version": "0.1.0",
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=0.12.0"
}
},
- "node_modules/base/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
+ "node_modules/base64-js": {
+ "version": "1.3.1",
+ "license": "MIT"
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base/node_modules/is-descriptor": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/base32.js": {
- "version": "0.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/base64-js": {
- "version": "1.3.1",
- "license": "MIT"
- },
- "node_modules/base64id": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
- "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
- "engines": {
- "node": "^4.5.0 || >= 5.9"
+ "node": "^4.5.0 || >= 5.9"
}
},
"node_modules/base64url": {
@@ -11662,102 +11238,6 @@
"dev": true,
"license": "BSD-2-Clause"
},
- "node_modules/browserify-aes": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-xor": "^1.0.3",
- "cipher-base": "^1.0.0",
- "create-hash": "^1.1.0",
- "evp_bytestokey": "^1.0.3",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/browserify-cipher": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserify-aes": "^1.0.4",
- "browserify-des": "^1.0.0",
- "evp_bytestokey": "^1.0.0"
- }
- },
- "node_modules/browserify-des": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cipher-base": "^1.0.1",
- "des.js": "^1.0.0",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
- "node_modules/browserify-rsa": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^5.0.0",
- "randombytes": "^2.0.1"
- }
- },
- "node_modules/browserify-rsa/node_modules/bn.js": {
- "version": "5.1.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/browserify-sign": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
- "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
- "dev": true,
- "dependencies": {
- "bn.js": "^5.2.1",
- "browserify-rsa": "^4.1.0",
- "create-hash": "^1.2.0",
- "create-hmac": "^1.1.7",
- "elliptic": "^6.5.4",
- "inherits": "^2.0.4",
- "parse-asn1": "^5.1.6",
- "readable-stream": "^3.6.2",
- "safe-buffer": "^5.2.1"
- },
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/browserify-sign/node_modules/bn.js": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
- "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==",
- "dev": true
- },
- "node_modules/browserify-sign/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "dev": true,
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/browserify-zlib": {
- "version": "0.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pako": "~1.0.5"
- }
- },
"node_modules/browserslist": {
"version": "4.23.3",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz",
@@ -11843,16 +11323,6 @@
"version": "1.1.1",
"license": "MIT"
},
- "node_modules/buffer-xor": {
- "version": "1.0.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/builtin-status-codes": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/busboy": {
"version": "1.6.0",
"dependencies": {
@@ -11870,25 +11340,6 @@
"node": ">= 0.8"
}
},
- "node_modules/cache-base": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "collection-visit": "^1.0.0",
- "component-emitter": "^1.2.1",
- "get-value": "^2.0.6",
- "has-value": "^1.0.0",
- "isobject": "^3.0.1",
- "set-value": "^2.0.0",
- "to-object-path": "^0.3.0",
- "union-value": "^1.0.0",
- "unset-value": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/call-bind": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
@@ -12020,22 +11471,6 @@
"fsevents": "~2.3.2"
}
},
- "node_modules/chownr": {
- "version": "1.1.4",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/chrome-trace-event": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tslib": "^1.9.0"
- },
- "engines": {
- "node": ">=6.0"
- }
- },
"node_modules/chromium-bidi": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.5.16.tgz",
@@ -12066,45 +11501,11 @@
"node": ">=8"
}
},
- "node_modules/cipher-base": {
- "version": "1.0.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- }
- },
"node_modules/cjs-module-lexer": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
"integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA=="
},
- "node_modules/class-utils": {
- "version": "0.3.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arr-union": "^3.1.0",
- "define-property": "^0.2.5",
- "isobject": "^3.0.0",
- "static-extend": "^0.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/class-utils/node_modules/define-property": {
- "version": "0.2.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
@@ -12258,20 +11659,6 @@
"node": ">=0.8"
}
},
- "node_modules/clone-deep": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
- "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
- "dev": true,
- "dependencies": {
- "is-plain-object": "^2.0.4",
- "kind-of": "^6.0.2",
- "shallow-clone": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -12288,18 +11675,6 @@
"integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==",
"dev": true
},
- "node_modules/collection-visit": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "map-visit": "^1.0.0",
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/color-convert": {
"version": "1.9.3",
"license": "MIT",
@@ -12359,61 +11734,10 @@
"node": ">= 0.8"
}
},
- "node_modules/commander": {
- "version": "2.20.3",
- "dev": true,
- "license": "MIT"
- },
"node_modules/commondir": {
"version": "1.0.1",
"license": "MIT"
},
- "node_modules/commoner": {
- "version": "0.10.8",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "commander": "^2.5.0",
- "detective": "^4.3.1",
- "glob": "^5.0.15",
- "graceful-fs": "^4.1.2",
- "iconv-lite": "^0.4.5",
- "mkdirp": "^0.5.0",
- "private": "^0.1.6",
- "q": "^1.1.2",
- "recast": "^0.11.17"
- },
- "bin": {
- "commonize": "bin/commonize"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/commoner/node_modules/glob": {
- "version": "5.0.15",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "2 || 3",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/commoner/node_modules/q": {
- "version": "1.5.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.6.0",
- "teleport": ">=0.2.0"
- }
- },
"node_modules/component-emitter": {
"version": "1.3.0",
"dev": true,
@@ -12672,20 +11996,11 @@
"mongodb": "^4.1.0"
}
},
- "node_modules/console-browserify": {
- "version": "1.2.0",
- "dev": true
- },
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
- "node_modules/constants-browserify": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/content-disposition": {
"version": "0.5.4",
"license": "MIT",
@@ -12774,38 +12089,6 @@
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
- "node_modules/copy-concurrently": {
- "version": "1.0.5",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "aproba": "^1.1.1",
- "fs-write-stream-atomic": "^1.0.8",
- "iferr": "^0.1.5",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.5.4",
- "run-queue": "^1.0.0"
- }
- },
- "node_modules/copy-concurrently/node_modules/rimraf": {
- "version": "2.7.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/copy-descriptor": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/copyfiles": {
"version": "2.4.1",
"dev": true,
@@ -12907,40 +12190,6 @@
"node": ">= 0.10"
}
},
- "node_modules/create-ecdh": {
- "version": "4.0.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "elliptic": "^6.5.3"
- }
- },
- "node_modules/create-hash": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cipher-base": "^1.0.1",
- "inherits": "^2.0.1",
- "md5.js": "^1.3.4",
- "ripemd160": "^2.0.1",
- "sha.js": "^2.4.0"
- }
- },
- "node_modules/create-hmac": {
- "version": "1.1.7",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cipher-base": "^1.0.3",
- "create-hash": "^1.1.0",
- "inherits": "^2.0.1",
- "ripemd160": "^2.0.0",
- "safe-buffer": "^5.0.1",
- "sha.js": "^2.4.8"
- }
- },
"node_modules/create-require": {
"version": "1.1.1",
"dev": true,
@@ -12982,27 +12231,6 @@
"node": ">= 8"
}
},
- "node_modules/crypto-browserify": {
- "version": "3.12.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "browserify-cipher": "^1.0.0",
- "browserify-sign": "^4.0.0",
- "create-ecdh": "^4.0.0",
- "create-hash": "^1.1.0",
- "create-hmac": "^1.1.0",
- "diffie-hellman": "^5.0.0",
- "inherits": "^2.0.1",
- "pbkdf2": "^3.0.3",
- "public-encrypt": "^4.0.0",
- "randombytes": "^2.0.0",
- "randomfill": "^1.0.3"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/crypto-randomuuid": {
"version": "1.0.0",
"license": "MIT"
@@ -13040,11 +12268,6 @@
"version": "2.1.8",
"license": "MIT"
},
- "node_modules/cyclist": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT"
- },
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
@@ -13286,15 +12509,6 @@
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
},
- "node_modules/decode-uri-component": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
- "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/dedent": {
"version": "0.7.0",
"dev": true,
@@ -13369,61 +12583,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/define-property": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^1.0.2",
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/define-property/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/define-property/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/define-property/node_modules/is-descriptor": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/defined": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
@@ -13495,15 +12654,6 @@
"node": ">= 0.8"
}
},
- "node_modules/des.js": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.1",
- "minimalistic-assert": "^1.0.0"
- }
- },
"node_modules/destroy": {
"version": "1.2.0",
"license": "MIT",
@@ -13528,26 +12678,6 @@
"node": ">=8"
}
},
- "node_modules/detective": {
- "version": "4.7.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "acorn": "^5.2.1",
- "defined": "^1.0.0"
- }
- },
- "node_modules/detective/node_modules/acorn": {
- "version": "5.7.4",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
"node_modules/devtools-protocol": {
"version": "0.0.1262051",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1262051.tgz",
@@ -13580,16 +12710,6 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
- "node_modules/diffie-hellman": {
- "version": "5.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "miller-rabin": "^4.0.0",
- "randombytes": "^2.0.0"
- }
- },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -13634,15 +12754,6 @@
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
- "node_modules/domain-browser": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4",
- "npm": ">=1.2"
- }
- },
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
@@ -13698,17 +12809,6 @@
"url": "https://dotenvx.com"
}
},
- "node_modules/duplexify": {
- "version": "3.7.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.0.0",
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0",
- "stream-shift": "^1.0.0"
- }
- },
"node_modules/dynamic-dedupe": {
"version": "0.3.0",
"dev": true,
@@ -13940,18 +13040,6 @@
"node": ">= 6"
}
},
- "node_modules/envinfo": {
- "version": "7.11.1",
- "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz",
- "integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==",
- "dev": true,
- "bin": {
- "envinfo": "dist/cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
@@ -15212,15 +14300,6 @@
"node": ">=0.4.x"
}
},
- "node_modules/evp_bytestokey": {
- "version": "1.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "md5.js": "^1.3.4",
- "safe-buffer": "^5.1.1"
- }
- },
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
@@ -15274,58 +14353,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/expand-brackets": {
- "version": "2.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "debug": "^2.3.3",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "posix-character-classes": "^0.1.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expand-brackets/node_modules/debug": {
- "version": "2.6.9",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/expand-brackets/node_modules/define-property": {
- "version": "0.2.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expand-brackets/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/expand-brackets/node_modules/ms": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/expect": {
"version": "29.5.0",
"resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz",
@@ -15538,104 +14565,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/extend-shallow": {
- "version": "3.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assign-symbols": "^1.0.0",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extend-shallow/node_modules/is-extendable": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob": {
- "version": "2.0.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "array-unique": "^0.3.2",
- "define-property": "^1.0.0",
- "expand-brackets": "^2.1.4",
- "extend-shallow": "^2.0.1",
- "fragment-cache": "^0.2.1",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/define-property": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extglob/node_modules/is-descriptor": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
@@ -15736,15 +14665,6 @@
"fxparser": "src/cli/cli.js"
}
},
- "node_modules/fastest-levenshtein": {
- "version": "1.0.16",
- "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
- "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==",
- "dev": true,
- "engines": {
- "node": ">= 4.9.1"
- }
- },
"node_modules/fastestsmallesttextencoderdecoder": {
"version": "1.0.22",
"resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz",
@@ -15780,11 +14700,6 @@
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
- "node_modules/figgy-pudding": {
- "version": "3.5.2",
- "dev": true,
- "license": "ISC"
- },
"node_modules/figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -15911,19 +14826,6 @@
"node": ">= 0.8"
}
},
- "node_modules/find-cache-dir": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^2.0.0",
- "pkg-dir": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/find-up": {
"version": "4.1.0",
"license": "MIT",
@@ -15942,15 +14844,6 @@
"node": ">=8"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "bin": {
- "flat": "cli.js"
- }
- },
"node_modules/flat-cache": {
"version": "3.0.4",
"dev": true,
@@ -15989,15 +14882,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/flush-write-stream": {
- "version": "1.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "readable-stream": "^2.3.6"
- }
- },
"node_modules/fn.name": {
"version": "1.1.0",
"license": "MIT"
@@ -16034,14 +14918,6 @@
"is-callable": "^1.1.3"
}
},
- "node_modules/for-in": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/foreach": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
@@ -16136,17 +15012,6 @@
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.8.tgz",
"integrity": "sha512-nmDtNqmMZkOxu0M5hkrS9YA15/KPkYkILb6Axg9XBAoUoYEtzg+LFmVWqZrl9FNttsW0qIUpx9RCA9INbv+Bxw=="
},
- "node_modules/fragment-cache": {
- "version": "0.2.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "map-cache": "^0.2.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -16155,15 +15020,6 @@
"node": ">= 0.6"
}
},
- "node_modules/from2": {
- "version": "2.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.1",
- "readable-stream": "^2.0.0"
- }
- },
"node_modules/fs-extra": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
@@ -16212,17 +15068,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
- "node_modules/fs-write-stream-atomic": {
- "version": "1.0.10",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "iferr": "^0.1.5",
- "imurmurhash": "^0.1.4",
- "readable-stream": "1 || 2"
- }
- },
"node_modules/fs.realpath": {
"version": "1.0.0",
"license": "ISC"
@@ -16396,14 +15241,6 @@
"node": ">= 14"
}
},
- "node_modules/get-value": {
- "version": "2.0.6",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/glob": {
"version": "7.1.6",
"license": "ISC",
@@ -16604,95 +15441,6 @@
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
- "node_modules/has-value": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-value": "^2.0.6",
- "has-values": "^1.0.0",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^3.0.0",
- "kind-of": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/has-values/node_modules/is-number": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/has-values/node_modules/kind-of": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/hash-base": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.4",
- "readable-stream": "^3.6.0",
- "safe-buffer": "^5.2.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/hash-base/node_modules/readable-stream": {
- "version": "3.6.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/hash.js": {
"version": "1.1.7",
"license": "MIT",
@@ -16977,11 +15725,6 @@
"resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="
},
- "node_modules/https-browserify": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"license": "MIT",
@@ -17039,11 +15782,6 @@
"version": "1.1.13",
"license": "BSD-3-Clause"
},
- "node_modules/iferr": {
- "version": "0.1.5",
- "dev": true,
- "license": "MIT"
- },
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
@@ -17132,11 +15870,6 @@
"node": ">=0.8.19"
}
},
- "node_modules/infer-owner": {
- "version": "1.0.4",
- "dev": true,
- "license": "ISC"
- },
"node_modules/inflight": {
"version": "1.0.6",
"license": "ISC",
@@ -17173,15 +15906,6 @@
"node": ">= 0.4"
}
},
- "node_modules/interpret": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
- "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
- "dev": true,
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/intl-tel-input": {
"version": "12.4.0",
"license": "MIT",
@@ -17215,33 +15939,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/is-accessor-descriptor": {
- "version": "0.1.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-accessor-descriptor/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-accessor-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-arguments": {
"version": "1.1.1",
"license": "MIT",
@@ -17338,33 +16035,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-data-descriptor": {
- "version": "0.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-data-descriptor/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/is-data-descriptor/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-data-view": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz",
@@ -17395,35 +16065,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-descriptor": {
- "version": "0.1.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-accessor-descriptor": "^0.1.6",
- "is-data-descriptor": "^0.1.4",
- "kind-of": "^5.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-descriptor/node_modules/kind-of": {
- "version": "5.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-extendable": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"dev": true,
@@ -17518,17 +16159,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-plain-object": {
- "version": "2.0.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -17643,14 +16273,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-windows": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/isarray": {
"version": "1.0.0",
"license": "MIT"
@@ -17659,14 +16281,6 @@
"version": "2.0.0",
"license": "ISC"
},
- "node_modules/isobject": {
- "version": "3.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/isomorphic-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
@@ -20439,14 +19053,6 @@
"node": ">=12.0.0"
}
},
- "node_modules/kind-of": {
- "version": "6.0.3",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -20870,14 +19476,6 @@
"node": ">=4"
}
},
- "node_modules/loader-runner": {
- "version": "2.4.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4.3.0 <5.0.0 || >=5.10"
- }
- },
"node_modules/loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
@@ -21583,18 +20181,6 @@
"node": ">=12"
}
},
- "node_modules/make-dir": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "pify": "^4.0.1",
- "semver": "^5.6.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/make-error": {
"version": "1.3.6",
"dev": true,
@@ -21620,25 +20206,6 @@
"node": ">=7.6"
}
},
- "node_modules/map-cache": {
- "version": "0.2.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/map-visit": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "object-visit": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/marked": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz",
@@ -21661,18 +20228,8 @@
"react": "18.x"
}
},
- "node_modules/md5.js": {
- "version": "1.3.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hash-base": "^3.0.0",
- "inherits": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
- "node_modules/media-typer": {
- "version": "0.3.0",
+ "node_modules/media-typer": {
+ "version": "0.3.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
@@ -21749,18 +20306,6 @@
"node": ">=8.6"
}
},
- "node_modules/miller-rabin": {
- "version": "4.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.0.0",
- "brorand": "^1.0.1"
- },
- "bin": {
- "miller-rabin": "bin/miller-rabin"
- }
- },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -21878,54 +20423,11 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
- "node_modules/mississippi": {
- "version": "3.0.0",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "concat-stream": "^1.5.0",
- "duplexify": "^3.4.2",
- "end-of-stream": "^1.1.0",
- "flush-write-stream": "^1.0.0",
- "from2": "^2.1.0",
- "parallel-transform": "^1.1.0",
- "pump": "^3.0.0",
- "pumpify": "^1.3.3",
- "stream-each": "^1.1.0",
- "through2": "^2.0.0"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
},
- "node_modules/mixin-deep": {
- "version": "1.3.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "for-in": "^1.0.2",
- "is-extendable": "^1.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/mixin-deep/node_modules/is-extendable": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-plain-object": "^2.0.4"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/mkdirp": {
"version": "0.5.5",
"license": "MIT",
@@ -22219,30 +20721,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/move-concurrently": {
- "version": "1.0.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "aproba": "^1.1.1",
- "copy-concurrently": "^1.0.0",
- "fs-write-stream-atomic": "^1.0.8",
- "mkdirp": "^0.5.1",
- "rimraf": "^2.5.4",
- "run-queue": "^1.0.3"
- }
- },
- "node_modules/move-concurrently/node_modules/rimraf": {
- "version": "2.7.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
@@ -22367,27 +20845,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/nanomatch": {
- "version": "1.2.13",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "fragment-cache": "^0.2.1",
- "is-windows": "^1.0.2",
- "kind-of": "^6.0.2",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"dev": true,
@@ -22482,13 +20939,6 @@
}
}
},
- "node_modules/nocache": {
- "version": "3.0.4",
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- }
- },
"node_modules/node-abort-controller": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz",
@@ -22661,86 +21111,6 @@
"uuid": "dist/bin/uuid"
}
},
- "node_modules/node-libs-browser": {
- "version": "2.2.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "assert": "^1.1.1",
- "browserify-zlib": "^0.2.0",
- "buffer": "^4.3.0",
- "console-browserify": "^1.1.0",
- "constants-browserify": "^1.0.0",
- "crypto-browserify": "^3.11.0",
- "domain-browser": "^1.1.1",
- "events": "^3.0.0",
- "https-browserify": "^1.0.0",
- "os-browserify": "^0.3.0",
- "path-browserify": "0.0.1",
- "process": "^0.11.10",
- "punycode": "^1.2.4",
- "querystring-es3": "^0.2.0",
- "readable-stream": "^2.3.3",
- "stream-browserify": "^2.0.1",
- "stream-http": "^2.7.2",
- "string_decoder": "^1.0.0",
- "timers-browserify": "^2.0.4",
- "tty-browserify": "0.0.0",
- "url": "^0.11.0",
- "util": "^0.11.0",
- "vm-browserify": "^1.0.1"
- }
- },
- "node_modules/node-libs-browser/node_modules/buffer": {
- "version": "4.9.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "base64-js": "^1.0.2",
- "ieee754": "^1.1.4",
- "isarray": "^1.0.0"
- }
- },
- "node_modules/node-libs-browser/node_modules/events": {
- "version": "3.2.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.8.x"
- }
- },
- "node_modules/node-libs-browser/node_modules/inherits": {
- "version": "2.0.3",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/node-libs-browser/node_modules/punycode": {
- "version": "1.4.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-libs-browser/node_modules/url": {
- "version": "0.11.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "punycode": "1.3.2",
- "querystring": "0.2.0"
- }
- },
- "node_modules/node-libs-browser/node_modules/url/node_modules/punycode": {
- "version": "1.3.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/node-libs-browser/node_modules/util": {
- "version": "0.11.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "2.0.3"
- }
- },
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@@ -22847,46 +21217,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/object-copy": {
- "version": "0.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "copy-descriptor": "^0.1.0",
- "define-property": "^0.2.5",
- "kind-of": "^3.0.3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-copy/node_modules/define-property": {
- "version": "0.2.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-copy/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/object-copy/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object-hash": {
"version": "2.2.0",
"license": "MIT",
@@ -22911,17 +21241,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object-visit": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object.assign": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz",
@@ -22972,17 +21291,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object.pick": {
- "version": "1.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "isobject": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/object.values": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz",
@@ -23244,11 +21552,6 @@
"node": ">=8"
}
},
- "node_modules/os-browserify": {
- "version": "0.3.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/p-defer": {
"version": "1.0.0",
"dev": true,
@@ -23349,16 +21652,6 @@
"version": "1.0.11",
"license": "(MIT AND Zlib)"
},
- "node_modules/parallel-transform": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cyclist": "^1.0.1",
- "inherits": "^2.0.3",
- "readable-stream": "^2.1.5"
- }
- },
"node_modules/parent-module": {
"version": "1.0.1",
"dev": true,
@@ -23370,18 +21663,6 @@
"node": ">=6"
}
},
- "node_modules/parse-asn1": {
- "version": "5.1.6",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "asn1.js": "^5.2.0",
- "browserify-aes": "^1.0.0",
- "evp_bytestokey": "^1.0.0",
- "pbkdf2": "^3.0.3",
- "safe-buffer": "^5.1.1"
- }
- },
"node_modules/parse-github-url": {
"version": "1.0.2",
"dev": true,
@@ -23450,25 +21731,6 @@
"node": ">= 0.8"
}
},
- "node_modules/pascalcase": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-browserify": {
- "version": "0.0.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-dirname": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
"node_modules/path-exists": {
"version": "3.0.0",
"dev": true,
@@ -23535,21 +21797,6 @@
"node": ">=8"
}
},
- "node_modules/pbkdf2": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "create-hash": "^1.1.2",
- "create-hmac": "^1.1.4",
- "ripemd160": "^2.0.1",
- "safe-buffer": "^5.0.1",
- "sha.js": "^2.4.8"
- },
- "engines": {
- "node": ">=0.12"
- }
- },
"node_modules/peberminta": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz",
@@ -23590,14 +21837,6 @@
"node": ">=0.10"
}
},
- "node_modules/pify": {
- "version": "4.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/pino": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz",
@@ -23702,58 +21941,13 @@
"node": ">=4"
}
},
- "node_modules/pkg-dir": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-dir/node_modules/find-up": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-dir/node_modules/locate-path": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-dir/node_modules/p-locate": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/playwright": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.1.tgz",
- "integrity": "sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
+ "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
"dev": true,
"dependencies": {
- "playwright-core": "1.45.1"
+ "playwright-core": "1.49.0"
},
"bin": {
"playwright": "cli.js"
@@ -23766,9 +21960,9 @@
}
},
"node_modules/playwright-core": {
- "version": "1.45.1",
- "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.1.tgz",
- "integrity": "sha512-LF4CUUtrUu2TCpDw4mcrAIuYrEjVDfT1cHbJMfwnE2+1b8PZcFzPNgvZCvq2JfQ4aTjRCCHw5EJ2tmr2NSzdPg==",
+ "version": "1.49.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
+ "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -23777,14 +21971,6 @@
"node": ">=18"
}
},
- "node_modules/posix-character-classes": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -23976,14 +22162,6 @@
"node": ">=6"
}
},
- "node_modules/private": {
- "version": "0.1.8",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/process": {
"version": "0.11.10",
"license": "MIT",
@@ -24008,11 +22186,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/promise-inflight": {
- "version": "1.0.1",
- "dev": true,
- "license": "ISC"
- },
"node_modules/promise-retry": {
"version": "2.0.1",
"license": "MIT",
@@ -24143,19 +22316,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/public-encrypt": {
- "version": "4.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bn.js": "^4.1.0",
- "browserify-rsa": "^4.0.0",
- "create-hash": "^1.1.0",
- "parse-asn1": "^5.0.0",
- "randombytes": "^2.0.1",
- "safe-buffer": "^5.1.2"
- }
- },
"node_modules/pump": {
"version": "3.0.0",
"license": "MIT",
@@ -24164,25 +22324,6 @@
"once": "^1.3.1"
}
},
- "node_modules/pumpify": {
- "version": "1.5.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "duplexify": "^3.6.0",
- "inherits": "^2.0.3",
- "pump": "^2.0.0"
- }
- },
- "node_modules/pumpify/node_modules/pump": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "once": "^1.3.1"
- }
- },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -24262,13 +22403,6 @@
"node": ">=0.4.x"
}
},
- "node_modules/querystring-es3": {
- "version": "0.2.1",
- "dev": true,
- "engines": {
- "node": ">=0.4.x"
- }
- },
"node_modules/querystringify": {
"version": "2.2.0",
"license": "MIT"
@@ -24291,23 +22425,6 @@
"node": ">= 0.8"
}
},
- "node_modules/randombytes": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
- "node_modules/randomfill": {
- "version": "1.0.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "randombytes": "^2.0.5",
- "safe-buffer": "^5.1.0"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -24641,52 +22758,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/recast": {
- "version": "0.11.23",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ast-types": "0.9.6",
- "esprima": "~3.1.0",
- "private": "~0.1.5",
- "source-map": "~0.5.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/recast/node_modules/ast-types": {
- "version": "0.9.6",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/recast/node_modules/esprima": {
- "version": "3.1.3",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/rechoir": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
- "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
- "dev": true,
- "dependencies": {
- "resolve": "^1.9.0"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -24705,51 +22776,6 @@
"node": ">=4"
}
},
- "node_modules/regenerator": {
- "version": "0.14.10",
- "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.14.10.tgz",
- "integrity": "sha512-gkjFZExU9FbaGlQ89aK49ZJ1bLl0C+8yKGrDrAwxArwiKlDeWJi/VH4Ao1Q//yCGk2axXMJcYrPp0WXmjTX+vw==",
- "dev": true,
- "dependencies": {
- "@babel/core": "^7.8.4",
- "@babel/runtime": "^7.8.4",
- "@babel/types": "^7.8.3",
- "commoner": "^0.10.8",
- "private": "^0.1.8",
- "recast": "^0.21.5",
- "regenerator-preset": "^0.14.1",
- "regenerator-runtime": "^0.13.11",
- "regenerator-transform": "^0.15.1",
- "through": "^2.3.8"
- },
- "bin": {
- "regenerator": "bin/regenerator"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/regenerator-preset": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-preset/-/regenerator-preset-0.14.1.tgz",
- "integrity": "sha512-7zEo8AK6N87TvOtFXEGec+LAGvCjgZyP/pbxm5NYAoWsmQCCd/IUKBwBqoOffiIhr1UOlgCGsRtwlR71Hl3Ewg==",
- "dev": true,
- "dependencies": {
- "@babel/plugin-proposal-function-sent": "^7.8.3",
- "@babel/plugin-syntax-async-generators": "^7.8.4",
- "@babel/plugin-transform-arrow-functions": "^7.8.3",
- "@babel/plugin-transform-block-scoping": "^7.8.3",
- "@babel/plugin-transform-classes": "^7.8.3",
- "@babel/plugin-transform-for-of": "^7.8.4",
- "regenerator-transform": "^0.15.0"
- }
- },
- "node_modules/regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
- "dev": true
- },
"node_modules/regenerator-transform": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz",
@@ -24759,57 +22785,6 @@
"@babel/runtime": "^7.8.4"
}
},
- "node_modules/regenerator/node_modules/esprima": {
- "version": "4.0.1",
- "dev": true,
- "license": "BSD-2-Clause",
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regenerator/node_modules/recast": {
- "version": "0.21.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ast-types": "0.15.2",
- "esprima": "~4.0.0",
- "source-map": "~0.6.1",
- "tslib": "^2.0.1"
- },
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/regenerator/node_modules/source-map": {
- "version": "0.6.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/regenerator/node_modules/tslib": {
- "version": "2.4.0",
- "dev": true,
- "license": "0BSD"
- },
- "node_modules/regex-not": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^3.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -24878,28 +22853,6 @@
"jsesc": "bin/jsesc"
}
},
- "node_modules/remove-trailing-separator": {
- "version": "1.1.0",
- "dev": true,
- "license": "ISC",
- "optional": true
- },
- "node_modules/repeat-element": {
- "version": "1.1.3",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/repeat-string": {
- "version": "1.6.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/require-directory": {
"version": "2.1.1",
"license": "MIT",
@@ -24964,11 +22917,6 @@
"node": ">=8"
}
},
- "node_modules/resolve-url": {
- "version": "0.2.1",
- "dev": true,
- "license": "MIT"
- },
"node_modules/resolve.exports": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz",
@@ -24990,14 +22938,6 @@
"node": ">=8"
}
},
- "node_modules/ret": {
- "version": "0.1.15",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.12"
- }
- },
"node_modules/retry": {
"version": "0.12.0",
"license": "MIT",
@@ -25084,15 +23024,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/ripemd160": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "hash-base": "^3.0.0",
- "inherits": "^2.0.1"
- }
- },
"node_modules/rrweb-cssom": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
@@ -25103,14 +23034,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/run-queue": {
- "version": "1.0.3",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "aproba": "^1.1.1"
- }
- },
"node_modules/rxjs": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
@@ -25167,14 +23090,6 @@
],
"license": "MIT"
},
- "node_modules/safe-regex": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ret": "~0.1.10"
- }
- },
"node_modules/safe-regex-test": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
@@ -25303,14 +23218,6 @@
"node": ">= 0.8"
}
},
- "node_modules/serialize-javascript": {
- "version": "3.1.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -25368,31 +23275,6 @@
"node": ">= 0.4"
}
},
- "node_modules/set-value": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.3",
- "split-string": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/set-value/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/setimmediate": {
"version": "1.0.5",
"license": "MIT"
@@ -25401,30 +23283,6 @@
"version": "1.2.0",
"license": "ISC"
},
- "node_modules/sha.js": {
- "version": "2.4.11",
- "dev": true,
- "license": "(MIT AND BSD-3-Clause)",
- "dependencies": {
- "inherits": "^2.0.1",
- "safe-buffer": "^5.0.1"
- },
- "bin": {
- "sha.js": "bin.js"
- }
- },
- "node_modules/shallow-clone": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
- "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
- "dev": true,
- "dependencies": {
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -25597,172 +23455,33 @@
"node": ">=6.0.0"
}
},
- "node_modules/snapdragon": {
- "version": "0.8.2",
+ "node_modules/sns-validator": {
+ "version": "0.3.5",
+ "license": "Apache-2.0"
+ },
+ "node_modules/socket.io": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz",
+ "integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==",
"dev": true,
- "license": "MIT",
"dependencies": {
- "base": "^0.11.1",
- "debug": "^2.2.0",
- "define-property": "^0.2.5",
- "extend-shallow": "^2.0.1",
- "map-cache": "^0.2.2",
- "source-map": "^0.5.6",
- "source-map-resolve": "^0.5.0",
- "use": "^3.1.0"
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "debug": "~4.3.2",
+ "engine.io": "~6.4.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.1"
},
"engines": {
- "node": ">=0.10.0"
+ "node": ">=10.0.0"
}
},
- "node_modules/snapdragon-node": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
+ "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"dependencies": {
- "define-property": "^1.0.0",
- "isobject": "^3.0.0",
- "snapdragon-util": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/define-property": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-data-descriptor": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^6.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-node/node_modules/is-descriptor": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-accessor-descriptor": "^1.0.0",
- "is-data-descriptor": "^1.0.0",
- "kind-of": "^6.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util": {
- "version": "3.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.2.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon-util/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/snapdragon-util/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon/node_modules/debug": {
- "version": "2.6.9",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/snapdragon/node_modules/define-property": {
- "version": "0.2.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/snapdragon/node_modules/ms": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/sns-validator": {
- "version": "0.3.5",
- "license": "Apache-2.0"
- },
- "node_modules/socket.io": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz",
- "integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==",
- "dev": true,
- "dependencies": {
- "accepts": "~1.3.4",
- "base64id": "~2.0.0",
- "debug": "~4.3.2",
- "engine.io": "~6.4.0",
- "socket.io-adapter": "~2.5.2",
- "socket.io-parser": "~4.2.1"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/socket.io-adapter": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
- "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
- "dependencies": {
- "ws": "~8.11.0"
+ "ws": "~8.11.0"
}
},
"node_modules/socket.io-parser": {
@@ -25829,19 +23548,6 @@
"flatstr": "^1.0.12"
}
},
- "node_modules/source-list-map": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/source-map": {
- "version": "0.5.7",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -25850,18 +23556,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/source-map-resolve": {
- "version": "0.5.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "atob": "^2.1.2",
- "decode-uri-component": "^0.2.0",
- "resolve-url": "^0.2.1",
- "source-map-url": "^0.4.0",
- "urix": "^0.1.0"
- }
- },
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -25880,11 +23574,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/source-map-url": {
- "version": "0.4.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/spark-md5": {
"version": "3.0.2",
"license": "(WTFPL OR MIT)"
@@ -25902,17 +23591,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/split-string": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
@@ -25987,29 +23665,6 @@
"node": ">=8"
}
},
- "node_modules/static-extend": {
- "version": "0.1.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-property": "^0.2.5",
- "object-copy": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/static-extend/node_modules/define-property": {
- "version": "0.2.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-descriptor": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/statuses": {
"version": "2.0.1",
"license": "MIT",
@@ -26017,41 +23672,6 @@
"node": ">= 0.8"
}
},
- "node_modules/stream-browserify": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "inherits": "~2.0.1",
- "readable-stream": "^2.0.2"
- }
- },
- "node_modules/stream-each": {
- "version": "1.2.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "end-of-stream": "^1.1.0",
- "stream-shift": "^1.0.0"
- }
- },
- "node_modules/stream-http": {
- "version": "2.8.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "builtin-status-codes": "^3.0.0",
- "inherits": "^2.0.1",
- "readable-stream": "^2.3.6",
- "to-arraybuffer": "^1.0.0",
- "xtend": "^4.0.0"
- }
- },
- "node_modules/stream-shift": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT"
- },
"node_modules/streamsearch": {
"version": "1.1.0",
"engines": {
@@ -26502,190 +24122,55 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
- "node_modules/terser": {
- "version": "4.8.0",
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
"dev": true,
- "license": "BSD-2-Clause",
"dependencies": {
- "commander": "^2.20.0",
- "source-map": "~0.6.1",
- "source-map-support": "~0.5.12"
- },
- "bin": {
- "terser": "bin/terser"
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
},
"engines": {
- "node": ">=6.0.0"
+ "node": ">=8"
}
},
- "node_modules/terser-webpack-plugin": {
- "version": "1.4.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cacache": "^12.0.2",
- "find-cache-dir": "^2.1.0",
- "is-wsl": "^1.1.0",
- "schema-utils": "^1.0.0",
- "serialize-javascript": "^3.1.0",
- "source-map": "^0.6.1",
- "terser": "^4.1.2",
- "webpack-sources": "^1.4.0",
- "worker-farm": "^1.7.0"
- },
- "engines": {
- "node": ">= 6.9.0"
- },
- "peerDependencies": {
- "webpack": "^4.0.0"
- }
+ "node_modules/text-encoding": {
+ "version": "0.7.0",
+ "license": "(Unlicense OR Apache-2.0)"
},
- "node_modules/terser-webpack-plugin/node_modules/cacache": {
- "version": "12.0.4",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "bluebird": "^3.5.5",
- "chownr": "^1.1.1",
- "figgy-pudding": "^3.5.1",
- "glob": "^7.1.4",
- "graceful-fs": "^4.1.15",
- "infer-owner": "^1.0.3",
- "lru-cache": "^5.1.1",
- "mississippi": "^3.0.0",
- "mkdirp": "^0.5.1",
- "move-concurrently": "^1.0.1",
- "promise-inflight": "^1.0.1",
- "rimraf": "^2.6.3",
- "ssri": "^6.0.1",
- "unique-filename": "^1.1.1",
- "y18n": "^4.0.0"
- }
+ "node_modules/text-hex": {
+ "version": "1.0.0",
+ "license": "MIT"
},
- "node_modules/terser-webpack-plugin/node_modules/is-wsl": {
- "version": "1.1.0",
+ "node_modules/text-table": {
+ "version": "0.2.0",
"dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
+ "license": "MIT"
},
- "node_modules/terser-webpack-plugin/node_modules/lru-cache": {
- "version": "5.1.1",
+ "node_modules/through": {
+ "version": "2.3.8",
+ "license": "MIT"
+ },
+ "node_modules/through2": {
+ "version": "2.0.5",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "yallist": "^3.0.2"
+ "readable-stream": "~2.3.6",
+ "xtend": "~4.0.1"
}
},
- "node_modules/terser-webpack-plugin/node_modules/rimraf": {
- "version": "2.7.1",
- "dev": true,
- "license": "ISC",
+ "node_modules/tldts": {
+ "version": "6.1.47",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.47.tgz",
+ "integrity": "sha512-R/K2tZ5MiY+mVrnSkNJkwqYT2vUv1lcT6wJvd2emGaMJ7PHUGRY4e3tUsdFCXgqxi2QgbHjL3yJgXCo40v9Hxw==",
"dependencies": {
- "glob": "^7.1.3"
+ "tldts-core": "^6.1.47"
},
"bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/terser-webpack-plugin/node_modules/schema-utils": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- },
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/terser-webpack-plugin/node_modules/source-map": {
- "version": "0.6.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/terser-webpack-plugin/node_modules/ssri": {
- "version": "6.0.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "figgy-pudding": "^3.5.1"
- }
- },
- "node_modules/terser/node_modules/source-map": {
- "version": "0.6.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/text-encoding": {
- "version": "0.7.0",
- "license": "(Unlicense OR Apache-2.0)"
- },
- "node_modules/text-hex": {
- "version": "1.0.0",
- "license": "MIT"
- },
- "node_modules/text-table": {
- "version": "0.2.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/through": {
- "version": "2.3.8",
- "license": "MIT"
- },
- "node_modules/through2": {
- "version": "2.0.5",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readable-stream": "~2.3.6",
- "xtend": "~4.0.1"
- }
- },
- "node_modules/timers-browserify": {
- "version": "2.0.12",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "setimmediate": "^1.0.4"
- },
- "engines": {
- "node": ">=0.6.0"
- }
- },
- "node_modules/tldts": {
- "version": "6.1.47",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.47.tgz",
- "integrity": "sha512-R/K2tZ5MiY+mVrnSkNJkwqYT2vUv1lcT6wJvd2emGaMJ7PHUGRY4e3tUsdFCXgqxi2QgbHjL3yJgXCo40v9Hxw==",
- "dependencies": {
- "tldts-core": "^6.1.47"
- },
- "bin": {
- "tldts": "bin/cli.js"
+ "tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
@@ -26704,11 +24189,6 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true
},
- "node_modules/to-arraybuffer": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT"
- },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"license": "MIT",
@@ -26716,47 +24196,6 @@
"node": ">=4"
}
},
- "node_modules/to-object-path": {
- "version": "0.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-object-path/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/to-object-path/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/to-regex": {
- "version": "3.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "regex-not": "^1.0.2",
- "safe-regex": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -27182,11 +24621,6 @@
"version": "1.13.0",
"license": "0BSD"
},
- "node_modules/tty-browserify": {
- "version": "0.0.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/tweetnacl": {
"version": "1.0.3",
"license": "Unlicense"
@@ -27478,36 +24912,6 @@
"node": ">=4"
}
},
- "node_modules/union-value": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arr-union": "^3.1.0",
- "get-value": "^2.0.6",
- "is-extendable": "^0.1.1",
- "set-value": "^2.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unique-filename": {
- "version": "1.1.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "unique-slug": "^2.0.0"
- }
- },
- "node_modules/unique-slug": {
- "version": "2.0.2",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "imurmurhash": "^0.1.4"
- }
- },
"node_modules/universalify": {
"version": "0.1.2",
"dev": true,
@@ -27536,50 +24940,6 @@
"node": ">= 0.8"
}
},
- "node_modules/unset-value": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has-value": "^0.3.1",
- "isobject": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value": {
- "version": "0.3.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "get-value": "^2.0.3",
- "has-values": "^0.1.4",
- "isobject": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-value/node_modules/isobject": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "isarray": "1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/unset-value/node_modules/has-values": {
- "version": "0.1.4",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/untildify": {
"version": "4.0.0",
"dev": true,
@@ -27588,16 +24948,6 @@
"node": ">=8"
}
},
- "node_modules/upath": {
- "version": "1.2.0",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=4",
- "yarn": "*"
- }
- },
"node_modules/update-browserslist-db": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
@@ -27647,11 +24997,6 @@
"integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==",
"dev": true
},
- "node_modules/urix": {
- "version": "0.1.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/url": {
"version": "0.10.3",
"license": "MIT",
@@ -27677,14 +25022,6 @@
"resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz",
"integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg=="
},
- "node_modules/use": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/util": {
"version": "0.12.5",
"license": "MIT",
@@ -27822,11 +25159,6 @@
"node": ">= 0.8"
}
},
- "node_modules/vm-browserify": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT"
- },
"node_modules/w3c-hr-time": {
"version": "1.0.2",
"dev": true,
@@ -27855,643 +25187,54 @@
"makeerror": "1.0.12"
}
},
- "node_modules/watchpack": {
- "version": "1.7.5",
- "dev": true,
- "license": "MIT",
+ "node_modules/wcwidth": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+ "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"dependencies": {
- "graceful-fs": "^4.1.2",
- "neo-async": "^2.5.0"
- },
- "optionalDependencies": {
- "chokidar": "^3.4.1",
- "watchpack-chokidar2": "^2.0.1"
+ "defaults": "^1.0.3"
}
},
- "node_modules/watchpack-chokidar2": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "chokidar": "^2.1.8"
+ "node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "engines": {
+ "node": ">= 14"
}
},
- "node_modules/watchpack-chokidar2/node_modules/anymatch": {
- "version": "2.0.0",
- "dev": true,
- "license": "ISC",
- "optional": true,
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"dependencies": {
- "micromatch": "^3.1.4",
- "normalize-path": "^2.1.1"
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
+ "node_modules/whatwg-encoding/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
- "remove-trailing-separator": "^1.0.1"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/watchpack-chokidar2/node_modules/binary-extensions": {
- "version": "1.13.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/braces": {
- "version": "2.3.2",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/chokidar": {
- "version": "2.1.8",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "anymatch": "^2.0.0",
- "async-each": "^1.0.1",
- "braces": "^2.3.2",
- "glob-parent": "^3.1.0",
- "inherits": "^2.0.3",
- "is-binary-path": "^1.0.0",
- "is-glob": "^4.0.0",
- "normalize-path": "^3.0.0",
- "path-is-absolute": "^1.0.0",
- "readdirp": "^2.2.1",
- "upath": "^1.1.1"
- },
- "optionalDependencies": {
- "fsevents": "^1.2.7"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/fill-range": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/fsevents": {
- "version": "1.2.13",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "dependencies": {
- "bindings": "^1.5.0",
- "nan": "^2.12.1"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/glob-parent": {
- "version": "3.1.0",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "dependencies": {
- "is-glob": "^3.1.0",
- "path-dirname": "^1.0.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": {
- "version": "3.1.0",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-extglob": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/is-binary-path": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "binary-extensions": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT",
- "optional": true
- },
- "node_modules/watchpack-chokidar2/node_modules/is-number": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/micromatch": {
- "version": "3.1.10",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/readdirp": {
- "version": "2.2.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "graceful-fs": "^4.1.11",
- "micromatch": "^3.1.10",
- "readable-stream": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/watchpack-chokidar2/node_modules/to-regex-range": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wcwidth": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
- "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
- "dependencies": {
- "defaults": "^1.0.3"
- }
- },
- "node_modules/web-streams-polyfill": {
- "version": "4.0.0-beta.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
- "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/webpack": {
- "version": "4.46.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@webassemblyjs/ast": "1.9.0",
- "@webassemblyjs/helper-module-context": "1.9.0",
- "@webassemblyjs/wasm-edit": "1.9.0",
- "@webassemblyjs/wasm-parser": "1.9.0",
- "acorn": "^6.4.1",
- "ajv": "^6.10.2",
- "ajv-keywords": "^3.4.1",
- "chrome-trace-event": "^1.0.2",
- "enhanced-resolve": "^4.5.0",
- "eslint-scope": "^4.0.3",
- "json-parse-better-errors": "^1.0.2",
- "loader-runner": "^2.4.0",
- "loader-utils": "^1.2.3",
- "memory-fs": "^0.4.1",
- "micromatch": "^3.1.10",
- "mkdirp": "^0.5.3",
- "neo-async": "^2.6.1",
- "node-libs-browser": "^2.2.1",
- "schema-utils": "^1.0.0",
- "tapable": "^1.1.3",
- "terser-webpack-plugin": "^1.4.3",
- "watchpack": "^1.7.4",
- "webpack-sources": "^1.4.1"
- },
- "bin": {
- "webpack": "bin/webpack.js"
- },
- "engines": {
- "node": ">=6.11.5"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependenciesMeta": {
- "webpack-cli": {
- "optional": true
- },
- "webpack-command": {
- "optional": true
- }
- }
- },
- "node_modules/webpack-cli": {
- "version": "4.10.0",
- "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
- "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
- "dev": true,
- "dependencies": {
- "@discoveryjs/json-ext": "^0.5.0",
- "@webpack-cli/configtest": "^1.2.0",
- "@webpack-cli/info": "^1.5.0",
- "@webpack-cli/serve": "^1.7.0",
- "colorette": "^2.0.14",
- "commander": "^7.0.0",
- "cross-spawn": "^7.0.3",
- "fastest-levenshtein": "^1.0.12",
- "import-local": "^3.0.2",
- "interpret": "^2.2.0",
- "rechoir": "^0.7.0",
- "webpack-merge": "^5.7.3"
- },
- "bin": {
- "webpack-cli": "bin/cli.js"
- },
- "engines": {
- "node": ">=10.13.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/webpack"
- },
- "peerDependencies": {
- "webpack": "4.x.x || 5.x.x"
- },
- "peerDependenciesMeta": {
- "@webpack-cli/generators": {
- "optional": true
- },
- "@webpack-cli/migrate": {
- "optional": true
- },
- "webpack-bundle-analyzer": {
- "optional": true
- },
- "webpack-dev-server": {
- "optional": true
- }
- }
- },
- "node_modules/webpack-cli/node_modules/commander": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
- "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
- "dev": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/webpack-merge": {
- "version": "5.10.0",
- "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
- "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
- "dev": true,
- "dependencies": {
- "clone-deep": "^4.0.1",
- "flat": "^5.0.2",
- "wildcard": "^2.0.0"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/webpack-sources": {
- "version": "1.4.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "source-list-map": "^2.0.0",
- "source-map": "~0.6.1"
- }
- },
- "node_modules/webpack-sources/node_modules/source-map": {
- "version": "0.6.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/acorn": {
- "version": "6.4.2",
- "dev": true,
- "license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/webpack/node_modules/braces": {
- "version": "2.3.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arr-flatten": "^1.1.0",
- "array-unique": "^0.3.2",
- "extend-shallow": "^2.0.1",
- "fill-range": "^4.0.0",
- "isobject": "^3.0.1",
- "repeat-element": "^1.1.2",
- "snapdragon": "^0.8.1",
- "snapdragon-node": "^2.0.1",
- "split-string": "^3.0.2",
- "to-regex": "^3.0.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/enhanced-resolve": {
- "version": "4.5.0",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.2",
- "memory-fs": "^0.5.0",
- "tapable": "^1.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/webpack/node_modules/enhanced-resolve/node_modules/memory-fs": {
- "version": "0.5.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "errno": "^0.1.3",
- "readable-stream": "^2.0.1"
- },
- "engines": {
- "node": ">=4.3.0 <5.0.0 || >=5.10"
- }
- },
- "node_modules/webpack/node_modules/eslint-scope": {
- "version": "4.0.3",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "esrecurse": "^4.1.0",
- "estraverse": "^4.1.1"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/webpack/node_modules/fill-range": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "extend-shallow": "^2.0.1",
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1",
- "to-regex-range": "^2.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-extendable": "^0.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/is-buffer": {
- "version": "1.1.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/webpack/node_modules/is-number": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "kind-of": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/is-number/node_modules/kind-of": {
- "version": "3.2.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-buffer": "^1.1.5"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/memory-fs": {
- "version": "0.4.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "errno": "^0.1.3",
- "readable-stream": "^2.0.1"
- }
- },
- "node_modules/webpack/node_modules/micromatch": {
- "version": "3.1.10",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "arr-diff": "^4.0.0",
- "array-unique": "^0.3.2",
- "braces": "^2.3.1",
- "define-property": "^2.0.2",
- "extend-shallow": "^3.0.2",
- "extglob": "^2.0.4",
- "fragment-cache": "^0.2.1",
- "kind-of": "^6.0.2",
- "nanomatch": "^1.2.9",
- "object.pick": "^1.3.0",
- "regex-not": "^1.0.0",
- "snapdragon": "^0.8.1",
- "to-regex": "^3.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/webpack/node_modules/schema-utils": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.1.0",
- "ajv-errors": "^1.0.0",
- "ajv-keywords": "^3.1.0"
- },
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/webpack/node_modules/to-regex-range": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-number": "^3.0.0",
- "repeat-string": "^1.6.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/whatwg-encoding": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
- "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
- "dependencies": {
- "iconv-lite": "0.6.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/whatwg-encoding/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/whatwg-fetch": {
- "version": "3.6.2",
+ "node_modules/whatwg-fetch": {
+ "version": "3.6.2",
"dev": true,
"license": "MIT"
},
@@ -28558,12 +25301,6 @@
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
- "node_modules/wildcard": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
- "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
- "dev": true
- },
"node_modules/winston": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz",
@@ -28708,14 +25445,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/worker-farm": {
- "version": "1.7.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "errno": "~0.1.7"
- }
- },
"node_modules/worker-loader": {
"version": "2.0.0",
"dev": true,
@@ -28917,11 +25646,6 @@
"node": ">=0.4"
}
},
- "node_modules/y18n": {
- "version": "4.0.1",
- "dev": true,
- "license": "ISC"
- },
"node_modules/yallist": {
"version": "3.1.1",
"license": "ISC"
diff --git a/package.json b/package.json
index f219792174..5f22099957 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "FormSG",
"description": "Form Manager for Government",
- "version": "6.165.0",
+ "version": "6.166.0",
"homepage": "https://form.gov.sg",
"authors": [
"FormSG "
@@ -122,7 +122,6 @@
"multiparty": ">=4.2.3",
"nan": "^2.19.0",
"neverthrow": "^8.0.0",
- "nocache": "^3.0.4",
"node-cache": "^5.1.2",
"nodemailer": "^6.9.16",
"openai": "^4.70.3",
@@ -160,7 +159,7 @@
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-env": "^7.25.4",
"@opengovsg/mockpass": "^4.3.4",
- "@playwright/test": "^1.45.1",
+ "@playwright/test": "^1.49.0",
"@stoplight/prism-cli": "^5.10.0",
"@types/bcrypt": "^5.0.0",
"@types/bluebird": "^3.5.42",
@@ -180,7 +179,6 @@
"@types/ip": "^1.1.0",
"@types/jest": "^29.5.1",
"@types/json-stringify-safe": "^5.0.3",
- "@types/jsonfile": "^6.1.1",
"@types/jsonwebtoken": "^9.0.7",
"@types/jwk-to-pem": "^2.0.3",
"@types/lodash": "^4.17.6",
@@ -225,7 +223,6 @@
"maildev": "^2.1.0",
"mockdate": "^3.0.5",
"prettier": "^3.3.3",
- "regenerator": "^0.14.10",
"rimraf": "^5.0.5",
"stripe-event-types": "^3.1.0",
"supertest": "^6.3.3",
@@ -236,8 +233,6 @@
"ts-node-dev": "^2.0.0",
"type-fest": "^4.17.0",
"typescript": "^5.4.5",
- "webpack": "^4.46.0",
- "webpack-cli": "^4.10.0",
"worker-loader": "^2.0.0"
},
"config": {
diff --git a/shared/constants/errors.ts b/shared/constants/errors.ts
index dae6390895..cbaf35133d 100644
--- a/shared/constants/errors.ts
+++ b/shared/constants/errors.ts
@@ -20,6 +20,21 @@ export const FORM_WHITELIST_SETTING_CONTAINS_DUPLICATES_ERROR_MESSAGE =
export const FORM_WHITELIST_SETTING_CONTAINS_INVALID_FORMAT_SUBMITTERID_ERROR_MESSAGE =
(exampleInvalidSubmitterId: string) =>
- `Your CSV contains NRIC/FIN/UEN(s) that are not in the correct format. (e.g, ${exampleInvalidSubmitterId})`
+ `Your CSV contains NRIC/FIN/UEN(s) that are not in the correct format (e.g, ${exampleInvalidSubmitterId}).`
export const FORM_WHITELIST_CONTAINS_EMPTY_ROWS_ERROR_MESSAGE = `Your CSV contains empty row(s).`
+
+export const CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE =
+ 'The options in your CSV file and selected field do not match.'
+
+export const CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE =
+ 'There are missing options and/or emails in your CSV file.'
+
+export const CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE =
+ 'Your CSV file contains entries that are not in the correct format.'
+
+export const CONDITIONAL_ROUTING_DUPLICATE_OPTIONS_ERROR_MESSAGE =
+ 'There are contains duplicate options in your CSV file.'
+
+export const CONDITIONAL_ROUTING_CSV_PARSE_ERROR_MESSAGE =
+ 'An error occurred when parsing your CSV file.'
diff --git a/shared/constants/field/myinfo/index.ts b/shared/constants/field/myinfo/index.ts
index 333bbe5b1e..68139b2cc8 100644
--- a/shared/constants/field/myinfo/index.ts
+++ b/shared/constants/field/myinfo/index.ts
@@ -53,12 +53,12 @@ export const types: MyInfoFieldBlock[] = [
},
{
name: MyInfoAttribute.Sex,
- value: 'Gender',
+ value: 'Sex',
category: 'personal',
verified: ['SG', 'PR', 'F'],
source: 'Immigration & Checkpoints Authority / Ministry of Manpower',
description:
- 'The gender of the form-filler. This field is verified by ICA for Singaporeans/PRs & foreigners on Long-Term Visit Pass, and by MOM for Employment Pass holders.',
+ 'The sex of the form-filler. This field is verified by ICA for Singaporeans/PRs & foreigners on Long-Term Visit Pass, and by MOM for Employment Pass holders.',
fieldType: BasicField.Dropdown,
fieldOptions: ['FEMALE', 'MALE', 'UNKNOWN'],
previewValue: 'MALE',
@@ -349,11 +349,11 @@ export const types: MyInfoFieldBlock[] = [
},
{
name: MyInfoAttribute.ChildGender,
- value: "Child's gender",
+ value: "Child's sex",
category: 'children',
verified: [],
source: 'Immigration & Checkpoints Authority',
- description: 'Gender',
+ description: 'Sex',
fieldType: BasicField.ShortText,
fieldOptions: ['FEMALE', 'MALE', 'UNKNOWN'],
previewValue: 'MALE',
@@ -394,30 +394,3 @@ export const types: MyInfoFieldBlock[] = [
]
export const MYINFO_ATTRIBUTE_MAP = keyBy(types, 'name')
-
-// TODO: remove after 28 Jun 2024 as this would have fully taken effect
-function updateLabelBasedOnDate() {
- const currentDate = new Date().toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- const targetDate = new Date('2024-06-28T00:00:00').toLocaleString('en-US', {
- timeZone: 'Asia/Singapore',
- })
- if (new Date(currentDate) >= new Date(targetDate)) {
- const sexAttribute = MYINFO_ATTRIBUTE_MAP[MyInfoAttribute.Sex]
- if (sexAttribute) {
- sexAttribute.description = 'Sex'
- sexAttribute.value = 'Sex'
- sexAttribute.description =
- 'The sex of the form-filler. This field is verified by ICA for Singaporeans/PRs & foreigners on Long-Term Visit Pass, and by MOM for Employment Pass holders.'
- }
- const childGenderAttribute =
- MYINFO_ATTRIBUTE_MAP[MyInfoAttribute.ChildGender]
- if (childGenderAttribute) {
- childGenderAttribute.value = "Child's Sex"
- childGenderAttribute.description = 'Sex'
- }
- }
-}
-
-updateLabelBasedOnDate()
diff --git a/shared/package-lock.json b/shared/package-lock.json
index 3a217a2944..7b0b65aaa4 100644
--- a/shared/package-lock.json
+++ b/shared/package-lock.json
@@ -554,9 +554,9 @@
}
},
"node_modules/libphonenumber-js": {
- "version": "1.11.14",
- "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.14.tgz",
- "integrity": "sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ=="
+ "version": "1.11.15",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.15.tgz",
+ "integrity": "sha512-M7+rtYi9l5RvMmHyjyoF3BHHUpXTYdJ0PezZGHNs0GyW1lO+K7jxlXpbdIb7a56h0nqLYdjIw+E+z0ciGaJP7g=="
},
"node_modules/lie": {
"version": "3.3.0",
diff --git a/shared/types/field/dropdownField.ts b/shared/types/field/dropdownField.ts
index c13675dd31..380af1d58a 100644
--- a/shared/types/field/dropdownField.ts
+++ b/shared/types/field/dropdownField.ts
@@ -3,4 +3,9 @@ import { BasicField, MyInfoableFieldBase } from './base'
export interface DropdownFieldBase extends MyInfoableFieldBase {
fieldType: BasicField.Dropdown
fieldOptions: string[]
+ // Note: `optionsToRecipientsMap` is attached to the field instead of MRF workflow step to:
+ // 1. Allow immediate assignment without requiring step creation
+ // (since step might not be created yet at the point of map assignment, leading to UX issues)
+ // 2. Prevent orphaned mappings if field is deleted
+ optionsToRecipientsMap?: Record
}
diff --git a/shared/types/form/workflow.ts b/shared/types/form/workflow.ts
index a317eb24f4..bc3846dee5 100644
--- a/shared/types/form/workflow.ts
+++ b/shared/types/form/workflow.ts
@@ -3,6 +3,7 @@ import { FormFieldDto } from '../field'
export enum WorkflowType {
Static = 'static',
Dynamic = 'dynamic',
+ Conditional = 'conditional',
}
export interface FormWorkflowStepBase {
@@ -21,7 +22,15 @@ export interface FormWorkflowStepDynamic extends FormWorkflowStepBase {
field: FormFieldDto['_id']
}
-export type FormWorkflowStep = FormWorkflowStepStatic | FormWorkflowStepDynamic
+export interface FormWorkflowStepConditional extends FormWorkflowStepBase {
+ workflow_type: WorkflowType.Conditional
+ conditional_field: FormFieldDto['_id']
+}
+
+export type FormWorkflowStep =
+ | FormWorkflowStepStatic
+ | FormWorkflowStepDynamic
+ | FormWorkflowStepConditional
export type FormWorkflow = Array
diff --git a/shared/types/user.ts b/shared/types/user.ts
index 6def658b50..e1622d89a2 100644
--- a/shared/types/user.ts
+++ b/shared/types/user.ts
@@ -23,6 +23,7 @@ export const UserBase = z.object({
postmanSms: z.boolean().optional(),
mrfEmailNotifications: z.boolean().optional(), // Previously used for MRF email notifications, not currently used
mrfAdminSubmissionKey: z.boolean().optional(),
+ mrfConditionalRouting: z.boolean().optional(),
mfb: z.boolean().optional(),
})
.optional(),
diff --git a/shared/utils/__tests__/options-recipients-map-validation.spec.ts b/shared/utils/__tests__/options-recipients-map-validation.spec.ts
new file mode 100644
index 0000000000..9c994b51dc
--- /dev/null
+++ b/shared/utils/__tests__/options-recipients-map-validation.spec.ts
@@ -0,0 +1,16 @@
+import { checkIsOptionsMismatched } from '../options-recipients-map-validation'
+
+describe('checkIsOptionsMismatched', () => {
+ it('should return true if options have intersection but mismatched', () => {
+ expect(checkIsOptionsMismatched(['a', 'b'], ['b', 'c'])).toBe(true)
+ })
+ it('should return true if options in csv are missing from selected field', () => {
+ expect(checkIsOptionsMismatched(['a', 'b'], ['b'])).toBe(true)
+ })
+ it('should return true if options in selected field are missing from csv', () => {
+ expect(checkIsOptionsMismatched(['a', 'b'], ['a', 'b', 'c'])).toBe(true)
+ })
+ it('should return false if options are exactly the same', () => {
+ expect(checkIsOptionsMismatched(['a', 'b'], ['a', 'b'])).toBe(false)
+ })
+})
diff --git a/shared/utils/options-recipients-map-validation.ts b/shared/utils/options-recipients-map-validation.ts
new file mode 100644
index 0000000000..88aad5ecf2
--- /dev/null
+++ b/shared/utils/options-recipients-map-validation.ts
@@ -0,0 +1,19 @@
+const checkMissingElement = (actual: string[], expected: Set) => {
+ return actual.some((option) => !expected.has(option))
+}
+
+export const checkIsOptionsMismatched = (
+ optionsToRecipientsMapOptions: string[],
+ selectedConditionalFieldOptions: string[],
+) => {
+ return (
+ checkMissingElement(
+ optionsToRecipientsMapOptions,
+ new Set(selectedConditionalFieldOptions),
+ ) ||
+ checkMissingElement(
+ selectedConditionalFieldOptions,
+ new Set(optionsToRecipientsMapOptions),
+ )
+ )
+}
diff --git a/src/app/models/field/dropdownField.ts b/src/app/models/field/dropdownField.ts
index 2732902c96..76c68384d7 100644
--- a/src/app/models/field/dropdownField.ts
+++ b/src/app/models/field/dropdownField.ts
@@ -7,6 +7,7 @@ import { MyInfoSchema } from './baseField'
const createDropdownFieldSchema = () => {
return new Schema({
fieldOptions: [String],
+ optionsToRecipientsMap: { type: Object, of: [String] },
myInfo: MyInfoSchema,
})
}
diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts
index 2678165ba0..af03450619 100644
--- a/src/app/models/form.server.model.ts
+++ b/src/app/models/form.server.model.ts
@@ -111,6 +111,7 @@ import LogicSchema, {
import { CustomFormLogoSchema, FormLogoSchema } from './form_logo.server.schema'
import { FORM_WHITELISTED_SUBMITTER_IDS_ID } from './form_whitelist.server.model'
import WorkflowStepSchema, {
+ WorkflowStepConditionalSchema,
WorkflowStepDynamicSchema,
WorkflowStepStaticSchema,
} from './form_workflow_step.server.schema'
@@ -398,6 +399,10 @@ MultirespondentFormWorkflowPath.discriminator(
WorkflowType.Dynamic,
WorkflowStepDynamicSchema,
)
+MultirespondentFormWorkflowPath.discriminator(
+ WorkflowType.Conditional,
+ WorkflowStepConditionalSchema,
+)
const compileFormModel = (db: Mongoose): IFormModel => {
const User = getUserModel(db)
diff --git a/src/app/models/form_workflow_step.server.schema.ts b/src/app/models/form_workflow_step.server.schema.ts
index bf84c557d8..a4b91b72d6 100644
--- a/src/app/models/form_workflow_step.server.schema.ts
+++ b/src/app/models/form_workflow_step.server.schema.ts
@@ -3,6 +3,7 @@ import validator from 'validator'
import { WorkflowType } from '../../../shared/types'
import {
+ IWorkflowStepConditionalSchema,
IWorkflowStepDynamicSchema,
IWorkflowStepSchema,
IWorkflowStepStaticSchema,
@@ -60,4 +61,12 @@ export const WorkflowStepDynamicSchema = new Schema(
},
)
+export const WorkflowStepConditionalSchema =
+ new Schema({
+ conditional_field: {
+ type: Schema.Types.ObjectId,
+ required: true,
+ },
+ })
+
export default WorkflowStepSchema
diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts
index e3fed82425..d4c1447c4d 100644
--- a/src/app/models/user.server.model.ts
+++ b/src/app/models/user.server.model.ts
@@ -77,6 +77,7 @@ const compileUserModel = (db: Mongoose) => {
postmanSms: Boolean,
mrfEmailNotifications: Boolean, // Previously used for MRF email notifications, not currently used
mrfAdminSubmissionKey: Boolean,
+ mrfConditionalRouting: Boolean,
mfb: Boolean,
},
flags: {
diff --git a/src/app/modules/core/core.errors.ts b/src/app/modules/core/core.errors.ts
index 3f114767e9..f412756124 100644
--- a/src/app/modules/core/core.errors.ts
+++ b/src/app/modules/core/core.errors.ts
@@ -27,6 +27,7 @@ export enum ErrorCodes {
FORM_RESPONDENT_NOT_WHITELISTED = 100008,
FORM_RESPONDENT_SINGLE_SUBMISSION_VALIDATION_FAILED = 100009,
FORM_UNEXPECTED_WHITELIST_SETTING_NOT_FOUND = 100010,
+ FORM_INVALID_RESPONSE_MODE = 100011,
// [100100 - 100199] Admin Form Errors (/modules/form/admin-form)
ADMIN_FORM_INVALID_FILE_TYPE = 100100,
ADMIN_FORM_EDIT_FIELD = 100101,
diff --git a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts
index 27954370f6..77a3c609b8 100644
--- a/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts
+++ b/src/app/modules/form/admin-form/__tests__/admin-form.service.spec.ts
@@ -46,6 +46,9 @@ import {
import { EditFormFieldParams } from 'src/types/api'
import {
+ CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
FORM_WHITELIST_CONTAINS_EMPTY_ROWS_ERROR_MESSAGE,
FORM_WHITELIST_SETTING_CONTAINS_DUPLICATES_ERROR_MESSAGE,
FORM_WHITELIST_SETTING_CONTAINS_INVALID_FORMAT_SUBMITTERID_ERROR_MESSAGE,
@@ -3072,4 +3075,291 @@ describe('admin-form.service', () => {
)
})
})
+
+ describe('updateOptionsToRecipientsMap', () => {
+ describe('validation of field', () => {
+ it('should return error if field is not a dropdown field', async () => {
+ // Arrange
+ const mockFormFields = [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.ShortText,
+ },
+ ]
+ const mockForm = {
+ _id: 'formId',
+ form_fields: mockFormFields,
+ updateFormFieldById: jest.fn().mockResolvedValue({
+ form_fields: [mockFormFields],
+ }),
+ } as unknown as IPopulatedForm
+
+ const optionsMap = {}
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ optionsMap,
+ )
+
+ // Assert
+ expect(result.isErr()).toBe(true)
+ expect(result._unsafeUnwrapErr()).toBeInstanceOf(EditFieldError)
+ })
+
+ it('should allow operation if field is a dropdown field', async () => {
+ // Arrange
+ const mockFormFields = [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ toObject: jest.fn().mockReturnValue({
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ }),
+ },
+ ]
+
+ const mockForm = {
+ _id: 'formId',
+ form_fields: mockFormFields,
+ updateFormFieldById: jest.fn().mockResolvedValue({
+ form_fields: mockFormFields,
+ }),
+ } as unknown as IPopulatedForm
+
+ const optionsMap = {
+ option1: ['test1@example.com'],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ optionsMap,
+ )
+
+ // Assert
+ expect(result.isOk()).toBe(true)
+ })
+ })
+
+ describe('validation of options to recipients map', () => {
+ it('should return error if options to recipients map has options that are not in the field options', async () => {
+ // Arrange
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', 'option2'],
+ },
+ ],
+ } as unknown as IPopulatedForm
+
+ const invalidOptionsMap = {
+ option1: ['test1@example.com'],
+ option2: ['test2@example.com'],
+ option3: ['test3@example.com'],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ invalidOptionsMap,
+ )
+
+ // Assert
+ expect(result.isErr()).toBe(true)
+ expect(result._unsafeUnwrapErr().message).toBe(
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
+ )
+ })
+
+ it('should return error if options to recipients map does not have options that are in the field options', async () => {
+ // Arrange
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', 'option2'],
+ },
+ ],
+ } as unknown as IPopulatedForm
+
+ const incompleteOptionsMap = {
+ option2: ['test2@example.com'],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ incompleteOptionsMap,
+ )
+
+ // Assert
+ expect(result.isErr()).toBe(true)
+ expect(result._unsafeUnwrapErr().message).toBe(
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
+ )
+ })
+
+ it('should return error if some options is empty string', async () => {
+ // Arrange
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', ''],
+ },
+ ],
+ } as unknown as IPopulatedForm
+
+ const emptyOptionsMap = {
+ '': ['test@example.com'],
+ option2: ['test2@example.com'],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ emptyOptionsMap,
+ )
+
+ // Assert
+ expect(result.isErr()).toBe(true)
+ expect(result._unsafeUnwrapErr().message).toBe(
+ CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE,
+ )
+ })
+
+ it('should return error if some recipients are invalid emails', async () => {
+ // Arrange
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [
+ {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ },
+ ],
+ } as unknown as IPopulatedForm
+
+ const invalidEmailsMap = {
+ option1: ['invalid-email', 'valid-email@example.com'],
+ option2: ['valid-email2@example.com'],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm,
+ 'fieldId',
+ invalidEmailsMap,
+ )
+
+ // Assert
+ expect(result.isErr()).toBe(true)
+ expect(result._unsafeUnwrapErr().message).toBe(
+ CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE,
+ )
+ })
+
+ it('should save if the options to recipients map is valid', async () => {
+ // Arrange
+ const mockFormField = {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', 'option2'],
+ toObject: jest.fn().mockReturnValue({
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', 'option2'],
+ }),
+ }
+
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [mockFormField],
+ updateFormFieldById: jest.fn().mockResolvedValue({
+ form_fields: [mockFormField],
+ }),
+ }
+
+ const validOptionsMap = {
+ option1: ['test@example.com'],
+ option2: [
+ 'test2@example.com',
+ 'test3@example.com',
+ 'test4@example.com',
+ ],
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm as unknown as IPopulatedForm,
+ 'fieldId',
+ validOptionsMap,
+ )
+
+ // Assert
+ expect(result.isOk()).toBe(true)
+ expect(mockForm.updateFormFieldById).toHaveBeenCalledWith('fieldId', {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1', 'option2'],
+ optionsToRecipientsMap: validOptionsMap,
+ })
+ })
+
+ it('should save if the options to recipients map is empty aka deleted', async () => {
+ // Arrange
+ const mockFormField = {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ toObject: jest.fn().mockReturnValue({
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ }),
+ }
+
+ const mockForm = {
+ _id: 'formId',
+ form_fields: [mockFormField],
+ updateFormFieldById: jest.fn().mockResolvedValue({
+ form_fields: [mockFormField],
+ }),
+ }
+
+ // Act
+ const result = await AdminFormService.updateOptionsToRecipientsMap(
+ mockForm as unknown as IPopulatedForm,
+ 'fieldId',
+ {},
+ )
+
+ // Assert
+ expect(result.isOk()).toBe(true)
+ expect(mockForm.updateFormFieldById).toHaveBeenCalledWith('fieldId', {
+ _id: 'fieldId',
+ fieldType: BasicField.Dropdown,
+ fieldOptions: ['option1'],
+ optionsToRecipientsMap: {},
+ })
+ })
+ })
+ })
})
diff --git a/src/app/modules/form/admin-form/admin-form.controller.ts b/src/app/modules/form/admin-form/admin-form.controller.ts
index 5ce6a1940f..53268049e3 100644
--- a/src/app/modules/form/admin-form/admin-form.controller.ts
+++ b/src/app/modules/form/admin-form/admin-form.controller.ts
@@ -2277,6 +2277,69 @@ export const handleUpdateFormField = [
_handleUpdateFormField,
]
+const _handleUpdateOptionsToRecipientsMap: ControllerHandler<
+ {
+ formId: string
+ fieldId: string
+ },
+ FormFieldDto | ErrorDto,
+ { optionsToRecipientsMap: Record }
+> = (req, res) => {
+ const { formId, fieldId } = req.params
+ const { optionsToRecipientsMap } = req.body
+ const sessionUserId = (req.session as AuthedSessionData).user._id
+
+ // Step 1: Retrieve currently logged in user.
+ return UserService.getPopulatedUserById(sessionUserId)
+ .andThen((user) =>
+ // Step 2: Retrieve form with write permission check.
+ AuthService.getFormAfterPermissionChecks({
+ user,
+ formId,
+ level: PermissionLevel.Write,
+ }),
+ )
+ .andThen((form) => {
+ return AdminFormService.updateOptionsToRecipientsMap(
+ form,
+ fieldId,
+ optionsToRecipientsMap,
+ )
+ })
+ .map((updatedFormField) =>
+ res.status(StatusCodes.OK).json(updatedFormField as FormFieldDto),
+ )
+ .mapErr((error) => {
+ logger.error({
+ message: 'Error occurred when updating options to recipients map',
+ meta: {
+ action: '_handleUpdateOptionsToRecipientsMap',
+ ...createReqMeta(req),
+ userId: sessionUserId,
+ formId,
+ fieldId,
+ optionsToRecipientsMap,
+ },
+ error,
+ })
+ const { errorMessage, statusCode } = mapRouteError(error)
+ return res.status(statusCode).json({ message: errorMessage })
+ })
+}
+
+export const handleUpdateOptionsToRecipientsMap = [
+ celebrate({
+ [Segments.BODY]: Joi.object({
+ optionsToRecipientsMap: Joi.object(),
+ }),
+ [Segments.PARAMS]: Joi.object({
+ formId: Joi.string().required(),
+ fieldId: Joi.string().required(),
+ }),
+ }),
+ _handleUpdateOptionsToRecipientsMap,
+] as ControllerHandler[]
+
/**
* NOTE: Exported for testing.
* Private handler for POST /forms/:formId/fields
diff --git a/src/app/modules/form/admin-form/admin-form.middlewares.ts b/src/app/modules/form/admin-form/admin-form.middlewares.ts
index 7621bdd6b3..c4c26708eb 100644
--- a/src/app/modules/form/admin-form/admin-form.middlewares.ts
+++ b/src/app/modules/form/admin-form/admin-form.middlewares.ts
@@ -82,6 +82,11 @@ export const createWorkflowStepValidator = celebrate({
}),
edit: Joi.array().items(Joi.string()).required(),
approval_field: Joi.string().optional(),
+ conditional_field: Joi.when('workflow_type', {
+ is: WorkflowType.Conditional,
+ then: Joi.string().required(),
+ otherwise: Joi.forbidden(),
+ }),
}),
[Segments.PARAMS]: Joi.object({
formId: Joi.string().required(),
@@ -105,6 +110,11 @@ export const updateWorkflowStepValidator = celebrate({
}),
edit: Joi.array().items(Joi.string().hex().length(24)).required(),
approval_field: Joi.string().optional(),
+ conditional_field: Joi.when('workflow_type', {
+ is: WorkflowType.Conditional,
+ then: Joi.string().required(),
+ otherwise: Joi.forbidden(),
+ }),
}),
[Segments.PARAMS]: Joi.object({
formId: Joi.string().required(),
diff --git a/src/app/modules/form/admin-form/admin-form.service.ts b/src/app/modules/form/admin-form/admin-form.service.ts
index 69ff7b5535..49cb88e44e 100644
--- a/src/app/modules/form/admin-form/admin-form.service.ts
+++ b/src/app/modules/form/admin-form/admin-form.service.ts
@@ -13,8 +13,13 @@ import {
EncryptedStringsMessageContentWithMyPrivateKey,
} from 'shared/utils/crypto'
import type { Except, Merge } from 'type-fest'
+import validator from 'validator'
import {
+ CONDITIONAL_ROUTING_DUPLICATE_OPTIONS_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE,
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
FORM_WHITELIST_CONTAINS_EMPTY_ROWS_ERROR_MESSAGE,
FORM_WHITELIST_SETTING_CONTAINS_DUPLICATES_ERROR_MESSAGE,
FORM_WHITELIST_SETTING_CONTAINS_INVALID_FORMAT_SUBMITTERID_ERROR_MESSAGE,
@@ -26,11 +31,13 @@ import { MYINFO_ATTRIBUTE_MAP } from '../../../../../shared/constants/field/myin
import {
AdminDashboardFormMetaDto,
BasicField,
+ DropdownFieldBase,
DuplicateFormOverwriteDto,
EndPageUpdateDto,
FieldCreateDto,
FieldUpdateDto,
FormAuthType,
+ FormFieldDto,
FormLogoState,
FormMetadata,
FormPermission,
@@ -49,6 +56,7 @@ import {
isMFinSeriesValid,
isNricValid,
} from '../../../../../shared/utils/nric-validation'
+import { checkIsOptionsMismatched } from '../../../../../shared/utils/options-recipients-map-validation'
import { isUenValid } from '../../../../../shared/utils/uen-validation'
import { EditFieldActions } from '../../../../shared/constants'
import {
@@ -96,6 +104,7 @@ import { MissingUserError } from '../../user/user.errors'
import * as UserService from '../../user/user.service'
import { removeFormsFromAllWorkspaces } from '../../workspace/workspace.service'
import {
+ FormInvalidResponseModeError,
FormNotFoundError,
FormWhitelistSettingNotFoundError,
LogicNotFoundError,
@@ -737,6 +746,153 @@ export const duplicateForm = (
)
}
+/**
+ * Validates the mapping between dropdown/radio field options and recipient emails
+ * @param optionsToRecipientsMap Object mapping field options to arrays of recipient emails
+ * @param selectedConditionalFieldOptions Array of valid options for the selected field
+ * @returns Ok if validation passes, Error with appropriate message if validation fails
+ *
+ * Performs the following validations:
+ * - No duplicate options in the mapping
+ * - All options have at least one recipient email
+ * - Options cannot be empty strings
+ * - All recipient emails are valid email addresses
+ * - All options in mapping exist in the field's options and vice versa
+ */
+
+const validateOptionsToRecipientsMap = (
+ optionsToRecipientsMap: Record,
+ selectedConditionalFieldOptions: string[],
+): Result => {
+ // Mapping is being removed
+ if (
+ !optionsToRecipientsMap ||
+ Object.entries(optionsToRecipientsMap).length === 0
+ ) {
+ return ok(undefined)
+ }
+
+ // Check for duplicate options
+ const options = Object.keys(optionsToRecipientsMap)
+ if (new Set(options).size !== options.length) {
+ return err(
+ new MalformedParametersError(
+ CONDITIONAL_ROUTING_DUPLICATE_OPTIONS_ERROR_MESSAGE,
+ ),
+ )
+ }
+ // Check if there are missing options or emails
+ for (const [option, recipients] of Object.entries(optionsToRecipientsMap)) {
+ // Check if all recipients are valid emails
+ if (
+ recipients.some((recipientEmail) => !validator.isEmail(recipientEmail))
+ ) {
+ return err(
+ new MalformedParametersError(
+ CONDITIONAL_ROUTING_INVALID_CSV_FORMAT_ERROR_MESSAGE,
+ ),
+ )
+ }
+
+ if (!option || !recipients || recipients.length <= 0) {
+ return err(
+ new MalformedParametersError(
+ CONDITIONAL_ROUTING_EMAILS_OPTIONS_MISSING_ERROR_MESSAGE,
+ ),
+ )
+ }
+ }
+
+ if (
+ checkIsOptionsMismatched(
+ Object.keys(optionsToRecipientsMap),
+ selectedConditionalFieldOptions,
+ )
+ ) {
+ return err(
+ new MalformedParametersError(
+ CONDITIONAL_ROUTING_MISMATCHED_OPTIONS_ERROR_MESSAGE,
+ ),
+ )
+ }
+
+ return ok(undefined)
+}
+
+export const updateOptionsToRecipientsMap = (
+ form: IPopulatedForm,
+ fieldId: string,
+ optionsToRecipientsMap: Record,
+): ResultAsync<
+ FormFieldSchema,
+ PossibleDatabaseError | FieldNotFoundError | MalformedParametersError
+> => {
+ const formFieldToUpdate = getFormFieldById(form.form_fields, fieldId)
+
+ if (!formFieldToUpdate) {
+ return errAsync(new FieldNotFoundError())
+ }
+
+ if (formFieldToUpdate.fieldType !== BasicField.Dropdown) {
+ return errAsync(
+ new EditFieldError(
+ 'Field is not a dropdown field, only dropdown fields contain options to recipients map',
+ ),
+ )
+ }
+
+ const validationResult = validateOptionsToRecipientsMap(
+ optionsToRecipientsMap,
+ formFieldToUpdate.fieldOptions,
+ )
+
+ if (validationResult.isErr()) {
+ logger.error({
+ message: 'Options to recipients map is invalid',
+ meta: {
+ action: 'updateOptionsToRecipientsMap',
+ formId: form._id,
+ fieldId,
+ },
+ error: validationResult.error,
+ })
+ return errAsync(validationResult.error)
+ }
+
+ const updatedFormField = {
+ ...formFieldToUpdate.toObject(),
+ optionsToRecipientsMap,
+ }
+
+ return ResultAsync.fromPromise(
+ form.updateFormFieldById(
+ fieldId,
+ updatedFormField as FormFieldDto,
+ ),
+ (error) => {
+ logger.error({
+ message: 'Error encountered while updating options to recipients map',
+ meta: {
+ action: 'updateOptionsToRecipientsMap',
+ formId: form._id,
+ fieldId,
+ },
+ error,
+ })
+ return transformMongoError(error)
+ },
+ ).andThen((updatedForm) => {
+ if (!updatedForm) {
+ return errAsync(new FieldNotFoundError())
+ }
+
+ const updatedFormField = getFormFieldById(updatedForm.form_fields, fieldId)
+ return updatedFormField
+ ? okAsync(updatedFormField)
+ : errAsync(new FieldNotFoundError())
+ })
+}
+
/**
* Updates the targeted form field with the new field provided
* @param form the form the field to update belongs to
@@ -1402,7 +1558,7 @@ export const createWorkflowStep = (
): ResultAsync => {
if (originalForm.responseMode !== FormResponseMode.Multirespondent) {
return errAsync(
- new MalformedParametersError(
+ new FormInvalidResponseModeError(
'Cannot update workflow step for non-multirespondent mode forms',
),
)
@@ -1524,7 +1680,7 @@ export const updateFormWorkflowStep = (
): ResultAsync => {
if (originalForm.responseMode !== FormResponseMode.Multirespondent) {
return errAsync(
- new MalformedParametersError(
+ new FormInvalidResponseModeError(
'Cannot update workflow step for non-multirespondent mode forms',
),
)
@@ -1653,7 +1809,7 @@ export const deleteFormWorkflowStep = (
): ResultAsync => {
if (originalForm.responseMode !== FormResponseMode.Multirespondent) {
return errAsync(
- new MalformedParametersError(
+ new FormInvalidResponseModeError(
'Cannot update workflow step for non-multirespondent mode forms',
),
)
diff --git a/src/app/modules/form/form.errors.ts b/src/app/modules/form/form.errors.ts
index a0e20cdf16..7c08bd12b3 100644
--- a/src/app/modules/form/form.errors.ts
+++ b/src/app/modules/form/form.errors.ts
@@ -11,6 +11,12 @@ export class FormNotFoundError extends ApplicationError {
}
}
+export class FormInvalidResponseModeError extends ApplicationError {
+ constructor(message = 'Invalid response mode') {
+ super(message, undefined, ErrorCodes.FORM_INVALID_RESPONSE_MODE)
+ }
+}
+
export class FormDeletedError extends ApplicationError {
constructor(message = 'This form is no longer active') {
super(message, undefined, ErrorCodes.FORM_DELETED)
diff --git a/src/app/modules/submission/multirespondent-submission/__tests__/multirespondent-submission.utils.spec.ts b/src/app/modules/submission/multirespondent-submission/__tests__/multirespondent-submission.utils.spec.ts
index 8401e4bb38..5b22c65008 100644
--- a/src/app/modules/submission/multirespondent-submission/__tests__/multirespondent-submission.utils.spec.ts
+++ b/src/app/modules/submission/multirespondent-submission/__tests__/multirespondent-submission.utils.spec.ts
@@ -10,11 +10,13 @@ import {
ChildBirthRecordsResponseV3,
EmailResponseV3,
FieldResponsesV3,
+ FormWorkflowStepDto,
LongTextResponseV3,
NumberResponseV3,
ShortTextResponseV3,
SubmissionType,
TableResponseV3,
+ WorkflowType,
} from 'shared/types'
import {
@@ -23,6 +25,7 @@ import {
ICheckboxFieldSchema,
IEmailFieldSchema,
INumberFieldSchema,
+ IPopulatedForm,
IShortTextFieldSchema,
ITableFieldSchema,
MultirespondentSubmissionData,
@@ -33,6 +36,7 @@ import { ValidateFieldErrorV3 } from '../../submission.errors'
import {
createMultirespondentSubmissionDto,
getQuestionTitleAnswerString,
+ retrieveWorkflowStepEmailAddresses,
validateMrfFieldResponses,
} from '../multirespondent-submission.utils'
@@ -352,6 +356,213 @@ describe('multirespondent-submission.utils', () => {
})
})
+ describe('retrieveWorkflowStepEmailAddresses', () => {
+ describe('conditional workflow type', () => {
+ it('should return correct emails for response for conditional routing workflow step', () => {
+ // Arrange
+ const mockConditionalFieldId = 'conditionalField'
+ const mockShortTextFieldId = 'shortTextField'
+ const mockForm = {
+ form_fields: [
+ generateDefaultField(BasicField.Dropdown, {
+ _id: mockConditionalFieldId,
+ fieldOptions: ['Option A', 'Option B'],
+ optionsToRecipientsMap: {
+ 'Option A': ['test1@example.com'],
+ 'Option B': ['test2@example.com', 'test3@example.com'],
+ },
+ }),
+ generateDefaultField(BasicField.ShortText, {
+ _id: mockShortTextFieldId,
+ }),
+ ],
+ } as IPopulatedForm
+
+ // Test Option A
+ const mockResponsesA = {
+ [mockConditionalFieldId]: {
+ fieldType: BasicField.Dropdown,
+ answer: 'Option A',
+ },
+ [mockShortTextFieldId]: {
+ fieldType: BasicField.ShortText,
+ answer: 'Some text response',
+ },
+ } as FieldResponsesV3
+
+ const mockWorkflowStep = {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: mockConditionalFieldId,
+ } as FormWorkflowStepDto
+
+ // Act & Assert for Option A
+ const resultA = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponsesA,
+ )
+ expect(resultA._unsafeUnwrap()).toEqual(['test1@example.com'])
+
+ // Test Option B
+ const mockResponsesB = {
+ [mockConditionalFieldId]: {
+ fieldType: BasicField.Dropdown,
+ answer: 'Option B',
+ },
+ [mockShortTextFieldId]: {
+ fieldType: BasicField.ShortText,
+ answer: 'Some text response',
+ },
+ } as FieldResponsesV3
+
+ // Act & Assert for Option B
+ const resultB = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponsesB,
+ )
+ expect(resultB._unsafeUnwrap()).toEqual([
+ 'test2@example.com',
+ 'test3@example.com',
+ ])
+ })
+
+ it('should return empty array if response for field id not found', () => {
+ // Arrange
+ const mockConditionalFieldId = 'conditionalField'
+ const mockForm = {
+ form_fields: [
+ generateDefaultField(BasicField.Dropdown, {
+ _id: mockConditionalFieldId,
+ fieldOptions: ['Option A', 'Option B'],
+ optionsToRecipientsMap: {
+ 'Option A': ['test1@example.com'],
+ 'Option B': ['test2@example.com'],
+ },
+ }),
+ ],
+ } as IPopulatedForm
+ const mockResponses = {} as FieldResponsesV3
+ const mockWorkflowStep = {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: mockConditionalFieldId,
+ } as FormWorkflowStepDto
+
+ // Act
+ const result = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponses,
+ )
+
+ // Assert
+ expect(result._unsafeUnwrap()).toEqual([])
+ })
+
+ it('should return empty array if response is not a dropdown field', () => {
+ // Arrange
+ const mockConditionalFieldId = 'conditionalField'
+ const mockForm = {
+ form_fields: [
+ generateDefaultField(BasicField.ShortText, {
+ _id: mockConditionalFieldId,
+ }),
+ ],
+ } as IPopulatedForm
+ const mockResponses = {
+ [mockConditionalFieldId]: {
+ fieldType: BasicField.ShortText,
+ answer: 'Some text',
+ },
+ } as FieldResponsesV3
+ const mockWorkflowStep = {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: mockConditionalFieldId,
+ } as FormWorkflowStepDto
+
+ // Act
+ const result = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponses,
+ )
+
+ // Assert
+ expect(result._unsafeUnwrap()).toEqual([])
+ })
+
+ it('should return empty array if no optionsToRecipientsMap is found for the conditional field', () => {
+ // Arrange
+ const mockConditionalFieldId = 'conditionalField'
+ const mockForm = {
+ form_fields: [
+ generateDefaultField(BasicField.Dropdown, {
+ _id: mockConditionalFieldId,
+ fieldOptions: ['Option A', 'Option B'],
+ }),
+ ],
+ } as IPopulatedForm
+ const mockResponses = {
+ [mockConditionalFieldId]: {
+ fieldType: BasicField.Dropdown,
+ answer: 'Option A',
+ },
+ } as FieldResponsesV3
+ const mockWorkflowStep = {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: mockConditionalFieldId,
+ } as FormWorkflowStepDto
+
+ // Act
+ const result = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponses,
+ )
+
+ // Assert
+ expect(result._unsafeUnwrap()).toEqual([])
+ })
+ })
+
+ it('should return an empty array if the optionsToRecipientsMap does not contain an email mapping for the option selected', () => {
+ // Arrange
+ const mockConditionalFieldId = 'conditionalField'
+ const mockForm = {
+ form_fields: [
+ generateDefaultField(BasicField.Dropdown, {
+ _id: mockConditionalFieldId,
+ fieldOptions: ['Option A', 'Option B'],
+ optionsToRecipientsMap: {
+ 'Option A': ['test1@example.com'],
+ 'Option B': ['test2@example.com'],
+ },
+ }),
+ ],
+ } as IPopulatedForm
+ const mockResponses = {
+ [mockConditionalFieldId]: {
+ fieldType: BasicField.Dropdown,
+ answer: 'Option C', // Option not in mapping
+ },
+ } as FieldResponsesV3
+ const mockWorkflowStep = {
+ workflow_type: WorkflowType.Conditional,
+ conditional_field: mockConditionalFieldId,
+ } as FormWorkflowStepDto
+
+ // Act
+ const result = retrieveWorkflowStepEmailAddresses(
+ mockForm,
+ mockWorkflowStep,
+ mockResponses,
+ )
+
+ // Assert
+ expect(result._unsafeUnwrap()).toEqual([])
+ })
+ })
+
it('should return an empty array when formFields is not iterable', () => {
const formFields = null as unknown as FormFieldSchema[]
const responses: FieldResponsesV3 = {
diff --git a/src/app/modules/submission/multirespondent-submission/multirespondent-submission.service.ts b/src/app/modules/submission/multirespondent-submission/multirespondent-submission.service.ts
index 48a3fa068e..81e3977fca 100644
--- a/src/app/modules/submission/multirespondent-submission/multirespondent-submission.service.ts
+++ b/src/app/modules/submission/multirespondent-submission/multirespondent-submission.service.ts
@@ -127,7 +127,7 @@ const sendNextStepEmail = ({
}): ResultAsync => {
const logMeta = {
action: 'sendNextStepEmail',
- formId,
+ formId: formId.toString(),
submissionId,
nextWorkflowStep: nextStepNumber,
}
@@ -139,7 +139,7 @@ const sendNextStepEmail = ({
return (
// Step 1: Retrieve email addresses for current workflow step
- retrieveWorkflowStepEmailAddresses(nextStep, responses)
+ retrieveWorkflowStepEmailAddresses(form, nextStep, responses)
.mapErr((error) => {
logger.error({
message: 'Failed to retrieve workflow step email addresses',
@@ -150,7 +150,13 @@ const sendNextStepEmail = ({
})
// Step 2: send out next workflow step email
.asyncAndThen((emails) => {
- if (!emails) return okAsync(true)
+ if (emails.length <= 0) {
+ logger.info({
+ message: 'No destination email found for MRF next step email',
+ meta: logMeta,
+ })
+ return okAsync(true)
+ }
return MailService.sendMRFWorkflowStepEmail({
emails,
formTitle,
@@ -185,7 +191,7 @@ const sendMrfOutcomeEmails = ({
}): ResultAsync => {
const logMeta = {
action: 'sendMrfOutcomeEmails',
- formId: form._id,
+ formId: form._id?.toString(),
submissionId,
}
const emailsToNotify =
@@ -216,7 +222,7 @@ const sendMrfOutcomeEmails = ({
// Step 1: Fetch email address from all workflow steps that are selected to notify
Result.combine(
validWorkflowStepsToNotify.map((workflowStep) =>
- retrieveWorkflowStepEmailAddresses(workflowStep, responses),
+ retrieveWorkflowStepEmailAddresses(form, workflowStep, responses),
),
)
.mapErr((error) => {
@@ -236,8 +242,13 @@ const sendMrfOutcomeEmails = ({
})
// Step 3: Send outcome emails based on type
.asyncAndThen((destinationEmails) => {
- if (!destinationEmails || destinationEmails.length <= 0)
+ if (!destinationEmails || destinationEmails.length <= 0) {
+ logger.info({
+ message: 'No destination email found for MRF outcome email',
+ meta: logMeta,
+ })
return okAsync(true)
+ }
const lastStepNumber = form.workflow.length - 1
const isLastStep = currentStepNumber === lastStepNumber
diff --git a/src/app/modules/submission/multirespondent-submission/multirespondent-submission.utils.ts b/src/app/modules/submission/multirespondent-submission/multirespondent-submission.utils.ts
index e6c799d565..b405842203 100644
--- a/src/app/modules/submission/multirespondent-submission/multirespondent-submission.utils.ts
+++ b/src/app/modules/submission/multirespondent-submission/multirespondent-submission.utils.ts
@@ -13,6 +13,7 @@ import {
} from '../../../../../shared/types'
import {
FormFieldSchema,
+ IPopulatedForm,
MultirespondentSubmissionData,
} from '../../../../types'
import { ParsedClearFormFieldResponsesV3 } from '../../../../types/api'
@@ -63,7 +64,39 @@ export const getEmailFromResponses = (
return field.answer.value
}
+const getConditionalFieldEmailRecipient = (
+ form: IPopulatedForm,
+ fieldId: string,
+ responses: FieldResponsesV3,
+): string[] => {
+ const conditionalField = form.form_fields.find(
+ (field) => field._id.toString() === fieldId.toString(),
+ )
+ const conditionalFieldResponse = responses[fieldId]
+
+ const isFieldValid =
+ !!conditionalField &&
+ conditionalField.fieldType === BasicField.Dropdown &&
+ !!conditionalField.optionsToRecipientsMap
+
+ const isResponseValid =
+ !!conditionalFieldResponse &&
+ conditionalFieldResponse.fieldType === BasicField.Dropdown
+
+ if (!isFieldValid || !isResponseValid) {
+ return [] // Not an error, misconfigured or respondent has not filled.
+ }
+
+ const emailRecipients =
+ conditionalField?.optionsToRecipientsMap?.[
+ conditionalFieldResponse.answer
+ ] ?? []
+
+ return emailRecipients
+}
+
export const retrieveWorkflowStepEmailAddresses = (
+ form: IPopulatedForm,
step: FormWorkflowStepDto,
responses: FieldResponsesV3,
): Result => {
@@ -77,6 +110,15 @@ export const retrieveWorkflowStepEmailAddresses = (
if (!email) return ok([])
return ok([email])
}
+ case WorkflowType.Conditional: {
+ return ok(
+ getConditionalFieldEmailRecipient(
+ form,
+ step.conditional_field,
+ responses,
+ ),
+ )
+ }
default: {
return err(new InvalidWorkflowTypeError())
}
diff --git a/src/app/routes/api/v3/admin/forms/admin-forms.form.routes.ts b/src/app/routes/api/v3/admin/forms/admin-forms.form.routes.ts
index ec44aaae91..a300e0591a 100644
--- a/src/app/routes/api/v3/admin/forms/admin-forms.form.routes.ts
+++ b/src/app/routes/api/v3/admin/forms/admin-forms.form.routes.ts
@@ -183,6 +183,11 @@ AdminFormsFormRouter.route(
*/
.get(AdminFormController.handleGetFormField)
+AdminFormsFormRouter.put(
+ '/:formId([a-fA-F0-9]{24})/fields/:fieldId([a-fA-F0-9]{24})/options-to-recipients-map',
+ AdminFormController.handleUpdateOptionsToRecipientsMap,
+)
+
/**
* Duplicates the form field with the fieldId from the specified form
* @security session
diff --git a/src/types/form_workflow_step.ts b/src/types/form_workflow_step.ts
index 29c9947bff..1b5ba401ea 100644
--- a/src/types/form_workflow_step.ts
+++ b/src/types/form_workflow_step.ts
@@ -2,6 +2,7 @@ import { Document } from 'mongoose'
import {
FormWorkflowStepBase,
+ FormWorkflowStepConditional,
FormWorkflowStepDynamic,
FormWorkflowStepStatic,
WorkflowType,
@@ -33,6 +34,11 @@ export interface IWorkflowStepDynamicSchema
edit: IFieldSchema['_id'][]
}
-export type FormWorkflowStepSchema =
- | IWorkflowStepStaticSchema
- | IWorkflowStepDynamicSchema
+export interface IWorkflowStepConditionalSchema
+ extends IWorkflowStepSchema,
+ FormWorkflowStepConditional,
+ Document {
+ workflow_type: WorkflowType.Conditional
+ edit: IFieldSchema['_id'][]
+ conditional_field: IFieldSchema['_id']
+}