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']} + /> + ) : ( + + ) + ) : 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. + + + + + + + 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'] +}