From f3aca9662e12c662fe0c4fdbd509ea6860ca6608 Mon Sep 17 00:00:00 2001 From: Adam Jones Date: Mon, 27 May 2024 23:17:48 +0100 Subject: [PATCH] Fix lint --- .gitignore | 2 +- package-lock.json | 983 ++++++++++++++++++++-------- server/package.json | 22 +- server/src/email.ts | 2 +- web/.eslintrc.js | 5 + web/package.json | 15 +- web/src/App.tsx | 94 ++- web/src/components/rhfDateField.tsx | 12 +- web/src/env/dev.ts | 12 +- web/src/env/local.template.ts | 12 +- web/src/env/prod.ts | 12 +- web/src/index.tsx | 12 +- web/src/pages/CountryForm.tsx | 24 +- web/src/pages/IsRegisteredForm.tsx | 26 +- web/src/pages/NeedsRegistration.tsx | 10 +- web/src/pages/NorthernIreland.tsx | 10 +- web/src/pages/PostalVoteForm.tsx | 387 ++++++----- web/src/pages/Start.test.tsx | 11 +- web/src/pages/Start.tsx | 8 +- web/src/react-app-env.d.ts | 4 +- web/src/types.ts | 6 +- 21 files changed, 1095 insertions(+), 574 deletions(-) create mode 100644 web/.eslintrc.js diff --git a/.gitignore b/.gitignore index 437427c..bf9d7ac 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ node_modules /coverage # production -/build +build # misc .DS_Store diff --git a/package-lock.json b/package-lock.json index 91d6253..a219f9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -659,93 +659,6 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/node-http-handler": { - "version": "3.374.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.374.0.tgz", - "integrity": "sha512-v1Z6m0wwkf65/tKuhwrtPRqVoOtNkDTRn2MBMtxCwEw+8V8Q+YRFqVgGN+J1n53ktE0G5OYVBux/NHiAjJHReQ==", - "deprecated": "This package has moved to @smithy/node-http-handler", - "dependencies": { - "@smithy/node-http-handler": "^1.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/abort-controller": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.1.0.tgz", - "integrity": "sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ==", - "dependencies": { - "@smithy/types": "^1.2.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/node-http-handler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.1.0.tgz", - "integrity": "sha512-d3kRriEgaIiGXLziAM8bjnaLn1fthCJeTLZIwEIpzQqe6yPX0a+yQoLCTyjb2fvdLwkMoG4p7THIIB5cj5lkbg==", - "dependencies": { - "@smithy/abort-controller": "^1.1.0", - "@smithy/protocol-http": "^1.2.0", - "@smithy/querystring-builder": "^1.1.0", - "@smithy/types": "^1.2.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/protocol-http": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.2.0.tgz", - "integrity": "sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q==", - "dependencies": { - "@smithy/types": "^1.2.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/querystring-builder": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.1.0.tgz", - "integrity": "sha512-gDEi4LxIGLbdfjrjiY45QNbuDmpkwh9DX4xzrR2AzjjXpxwGyfSpbJaYhXARw9p17VH0h9UewnNQXNwaQyYMDA==", - "dependencies": { - "@smithy/types": "^1.2.0", - "@smithy/util-uri-escape": "^1.1.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", - "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/node-http-handler/node_modules/@smithy/util-uri-escape": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.1.0.tgz", - "integrity": "sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.577.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.577.0.tgz", @@ -5313,9 +5226,9 @@ "dev": true }, "node_modules/@serverless/typescript": { - "version": "2.71.0", - "resolved": "https://registry.npmjs.org/@serverless/typescript/-/typescript-2.71.0.tgz", - "integrity": "sha512-Dh7zsUnBoBIZJbx2+oKd2E4pMEDGilgdcKU0fIctTa+mejI0zCOxwCtRQ4pNe6d5e/l5XDGwsU5N4w+fHPv79w==", + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@serverless/typescript/-/typescript-3.38.0.tgz", + "integrity": "sha512-2AZ7SwWNMOfe2sovoBf68FgiQlLH+RuS9MdSMAzXJ/Hx5d0tPZmmLxfUieF7gUGOExe/fhzCAW3akr6wTZuTpQ==", "dev": true }, "node_modules/@serverless/utils": { @@ -6781,12 +6694,6 @@ "@types/node": "*" } }, - "node_modules/@types/retry": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz", - "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -10225,15 +10132,6 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -12593,29 +12491,6 @@ "pend": "~1.2.0" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -13001,18 +12876,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/formidable": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", @@ -14474,6 +14337,39 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -15803,9 +15699,9 @@ } }, "node_modules/jose": { - "version": "4.15.5", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz", - "integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.3.0.tgz", + "integrity": "sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==", "dev": true, "funding": { "url": "https://github.com/sponsors/panva" @@ -16096,12 +15992,16 @@ } }, "node_modules/jsonpath-plus": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", - "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-8.1.0.tgz", + "integrity": "sha512-qVTiuKztFGw0dGhYi3WNqvddx3/SHtyDT0xJaeyz4uP0d1tkpG+0y5uYQ4OcIo1TLAz3PE/qDOW9F0uDt3+CTw==", "dev": true, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/jsonpath/node_modules/esprima": { @@ -17194,25 +17094,6 @@ "node": ">= 0.10.5" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -17734,22 +17615,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.2.tgz", - "integrity": "sha512-couX95waDu98NfNZV+i/iLt+fdVxmI7CbrrdC2uDWfPdUAApyxT4wmDlyOtR5KtTDmkDO0zDScDjDou9YHhd9g==", - "dev": true, - "dependencies": { - "@types/retry": "0.12.1", - "retry": "^0.13.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", @@ -21596,40 +21461,36 @@ } }, "node_modules/serverless-offline": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-12.0.4.tgz", - "integrity": "sha512-G256wDHI12vE0CJ0uTJMBlfnaN7o7td4GgClvQtuedt/n7vKoUfN0och+LybD6YVGsR5h1xpYjPPPLy2QFqWaA==", + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-13.6.0.tgz", + "integrity": "sha512-aSMg9TNfFjVj4QFmHqPzLwbwCSVmrpeoe96ozOXaesn2VX+4lyS+kTF1TXh4pZd5/ef9cfcma3jcqmgnShrUyA==", "dev": true, "dependencies": { - "@aws-sdk/client-lambda": "^3.241.0", - "@hapi/boom": "^10.0.0", - "@hapi/h2o2": "^10.0.0", - "@hapi/hapi": "^21.1.0", - "@serverless/utils": "^6.8.2", + "@aws-sdk/client-lambda": "^3.509.0", + "@hapi/boom": "^10.0.1", + "@hapi/h2o2": "^10.0.4", + "@hapi/hapi": "^21.3.3", "array-unflat-js": "^0.1.3", - "boxen": "^7.0.1", - "chalk": "^5.2.0", - "desm": "^1.3.0", - "execa": "^6.1.0", - "fs-extra": "^11.1.0", - "is-wsl": "^2.2.0", + "boxen": "^7.1.1", + "chalk": "^5.3.0", + "desm": "^1.3.1", + "execa": "^8.0.1", + "fs-extra": "^11.2.0", + "is-wsl": "^3.1.0", "java-invoke-local": "0.0.6", - "jose": "^4.11.2", + "jose": "^5.2.1", "js-string-escape": "^1.0.1", - "jsonpath-plus": "^7.2.0", + "jsonpath-plus": "^8.0.0", "jsonschema": "^1.4.1", "jszip": "^3.10.1", - "luxon": "^3.2.0", - "node-fetch": "^3.3.0", - "node-schedule": "^2.1.0", - "object.hasown": "^1.1.2", + "luxon": "^3.4.4", + "node-schedule": "^2.1.1", "p-memoize": "^7.1.1", - "p-retry": "^5.1.2", "velocityjs": "^2.0.6", - "ws": "^8.11.0" + "ws": "^8.16.0" }, "engines": { - "node": ">=14.18.0" + "node": ">=18.12.0" }, "peerDependencies": { "serverless": "^3.2.0" @@ -21657,23 +21518,23 @@ } }, "node_modules/serverless-offline/node_modules/execa": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-6.1.0.tgz", - "integrity": "sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^3.0.1", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", - "signal-exit": "^3.0.7", + "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -21693,13 +21554,25 @@ "node": ">=14.14" } }, + "node_modules/serverless-offline/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/serverless-offline/node_modules/human-signals": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-3.0.1.tgz", - "integrity": "sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, "engines": { - "node": ">=12.20.0" + "node": ">=16.17.0" } }, "node_modules/serverless-offline/node_modules/is-stream": { @@ -21714,34 +21587,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serverless-offline/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/serverless-offline/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, "engines": { - "node": ">=12" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/serverless-offline/node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "node_modules/serverless-offline/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serverless-offline/node_modules/npm-run-path": { @@ -21786,6 +21656,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serverless-offline/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/serverless-offline/node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -23848,6 +23730,17 @@ "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -24198,6 +24091,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -24715,15 +24609,6 @@ "resolved": "web", "link": true }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", @@ -25726,8 +25611,8 @@ "server": { "hasInstallScript": true, "dependencies": { - "@aws-sdk/client-ses": "^3.363.0", - "@aws-sdk/node-http-handler": "^3.127.0", + "@aws-sdk/client-ses": "^3.583.0", + "@smithy/node-http-handler": "^3.0.0", "ajv": "^8.11.0", "axios": "^1.6.0", "http-errors": "^1.8.0", @@ -25736,7 +25621,7 @@ "source-map-support": "^0.5.21" }, "devDependencies": { - "@serverless/typescript": "^2.61.0", + "@serverless/typescript": "^3.38.0", "@types/aws-lambda": "^8.10.71", "@types/http-errors": "^1.8.1", "@types/jest": "^27.0.2", @@ -25750,22 +25635,35 @@ "eslint-config-blvd": "^1.2.1", "fork-ts-checker-webpack-plugin": "^7.2.11", "jest": "^27.3.1", - "serverless": "^3.17.0", - "serverless-offline": "^12.0.3", - "serverless-offline-ses-v2": "^1.0.1", - "serverless-webpack": "^5.7.1", + "serverless": "^3.38.0", + "serverless-offline": "^13.6.0", + "serverless-offline-ses-v2": "^1.0.4", + "serverless-webpack": "^5.14.0", "shx": "^0.3.4", "ts-jest": "^27.0.7", - "ts-loader": "^9.2.6", - "ts-node": "^10.2.1", - "typescript": "^4.4.3", - "webpack": "^5.76.0", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.4.5", + "webpack": "^5.91.0", "webpack-node-externals": "^3.0.0" }, "engines": { "node": ">=14.15.0" } }, + "server/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "web": { "version": "0.1.0", "hasInstallScript": true, @@ -25781,6 +25679,7 @@ "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", "@types/styled-components": "^5.1.25", + "eslint-config-domdomegg": "^1.2.3", "govuk-react": "^0.10.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -25789,12 +25688,55 @@ "react-scripts": "^5.0.1", "signature_pad": "^4.0.7", "styled-components": "^5.3.5", - "typescript": "^4.7.4" + "typescript": "^5.4.5" }, "devDependencies": { "shx": "^0.3.4" } }, + "web/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "web/node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "web/node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "peer": true + }, "web/node_modules/@jest/types": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", @@ -25836,16 +25778,254 @@ "@types/yargs-parser": "*" } }, - "web/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "web/node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, "engines": { - "node": ">=10" + "node": "^16.0.0 || >=18.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "web/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "web/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "web/node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "web/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "web/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "web/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "web/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "web/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "web/node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "web/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "web/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "web/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "peer": true + }, + "web/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } }, "web/node_modules/diff-sequences": { "version": "28.1.1", @@ -25855,6 +26035,184 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "web/node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "web/node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-19.0.4.tgz", + "integrity": "sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "web/node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "web/node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "web/node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "web/node_modules/eslint-config-domdomegg": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/eslint-config-domdomegg/-/eslint-config-domdomegg-1.2.3.tgz", + "integrity": "sha512-OkVoSxctmpkF63vze8RHGvHMW8dijzI2k2s1mTLYMWHCDWGhabr+IToB+uT0w6AsoIN59yps6uhHK2iBJ2Y3AA==", + "dependencies": { + "@rushstack/eslint-patch": "^1.6.0", + "@typescript-eslint/eslint-plugin": "^6.13.2", + "@typescript-eslint/parser": "^6.13.2", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.1.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0" + }, + "peerDependencies": { + "eslint": "^8.55.0" + } + }, + "web/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "web/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "web/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "web/node_modules/expect": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", @@ -25870,6 +26228,34 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "web/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "web/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "web/node_modules/jest-diff": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", @@ -25941,6 +26327,69 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, + "web/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "web/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "peer": true + }, + "web/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "web/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "web/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "web/node_modules/pretty-format": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", @@ -25954,6 +26403,18 @@ "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } + }, + "web/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } } } } diff --git a/server/package.json b/server/package.json index 89f38d9..d6acc41 100644 --- a/server/package.json +++ b/server/package.json @@ -19,8 +19,8 @@ "node": ">=14.15.0" }, "dependencies": { - "@aws-sdk/client-ses": "^3.363.0", - "@aws-sdk/node-http-handler": "^3.127.0", + "@aws-sdk/client-ses": "^3.583.0", + "@smithy/node-http-handler": "^3.0.0", "ajv": "^8.11.0", "axios": "^1.6.0", "http-errors": "^1.8.0", @@ -29,7 +29,7 @@ "source-map-support": "^0.5.21" }, "devDependencies": { - "@serverless/typescript": "^2.61.0", + "@serverless/typescript": "^3.38.0", "@types/aws-lambda": "^8.10.71", "@types/http-errors": "^1.8.1", "@types/jest": "^27.0.2", @@ -43,16 +43,16 @@ "eslint-config-blvd": "^1.2.1", "fork-ts-checker-webpack-plugin": "^7.2.11", "jest": "^27.3.1", - "serverless": "^3.17.0", - "serverless-offline": "^12.0.3", - "serverless-offline-ses-v2": "^1.0.1", - "serverless-webpack": "^5.7.1", + "serverless": "^3.38.0", + "serverless-offline": "^13.6.0", + "serverless-offline-ses-v2": "^1.0.4", + "serverless-webpack": "^5.14.0", "shx": "^0.3.4", "ts-jest": "^27.0.7", - "ts-loader": "^9.2.6", - "ts-node": "^10.2.1", - "typescript": "^4.4.3", - "webpack": "^5.76.0", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", + "typescript": "^5.4.5", + "webpack": "^5.91.0", "webpack-node-externals": "^3.0.0" }, "jest": { diff --git a/server/src/email.ts b/server/src/email.ts index ea8bfae..7b08ce1 100644 --- a/server/src/email.ts +++ b/server/src/email.ts @@ -1,6 +1,6 @@ import nodemailer from 'nodemailer' import * as aws from '@aws-sdk/client-ses' -import { NodeHttpHandler } from '@aws-sdk/node-http-handler' +import { NodeHttpHandler } from '@smithy/node-http-handler' import env from './env/env' const requestHandler = new NodeHttpHandler({ diff --git a/web/.eslintrc.js b/web/.eslintrc.js new file mode 100644 index 0000000..499b3e5 --- /dev/null +++ b/web/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + extends: [ + 'eslint-config-domdomegg', + ], +}; diff --git a/web/package.json b/web/package.json index d24f4b6..d5c28b5 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", "@types/styled-components": "^5.1.25", + "eslint-config-domdomegg": "^1.2.3", "govuk-react": "^0.10.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -22,7 +23,10 @@ "react-scripts": "^5.0.1", "signature_pad": "^4.0.7", "styled-components": "^5.3.5", - "typescript": "^4.7.4" + "typescript": "^5.4.5" + }, + "devDependencies": { + "shx": "^0.3.4" }, "scripts": { "postinstall": "shx cp -n src/env/local.template.ts src/env/local.ts && shx cp -n src/env/local.ts src/env/env.ts", @@ -36,12 +40,6 @@ "eject": "react-scripts eject" }, "homepage": ".", - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, "browserslist": { "production": [ ">0.2%", @@ -53,8 +51,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "devDependencies": { - "shx": "^0.3.4" } } diff --git a/web/src/App.tsx b/web/src/App.tsx index 6149542..80ebcd2 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,61 +1,89 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheckToSlot } from '@fortawesome/free-solid-svg-icons'; -import { GlobalStyle, TopNav, Page, PhaseBanner, Link, GridRow, GridCol, Footer } from 'govuk-react'; +import { + GlobalStyle, TopNav, Page, PhaseBanner, Link, GridRow, GridCol, Footer, +} from 'govuk-react'; +import { + Routes, Route, Link as ReactRouterLink, useNavigate, +} from 'react-router-dom'; import Start from './pages/Start'; import IsRegisteredForm from './pages/IsRegisteredForm'; import NeedsRegistration from './pages/NeedsRegistration'; import PostalVoteForm from './pages/PostalVoteForm'; -import { Routes, Route, Link as ReactRouterLink, useNavigate } from 'react-router-dom'; import CountryForm from './pages/CountryForm'; import NorthernIreland from './pages/NorthernIreland'; import env from './env/env'; const App = () => { - const navigate = useNavigate() + const navigate = useNavigate(); return (
- }> - postal-vote - - } - serviceTitle={ - Apply for a postal vote - } + company={( + + }> + postal-vote + + +)} + serviceTitle={( + + Apply for a postal vote + +)} />
This is a new service – your feedback will help us to improve it. - {env.STAGE !== "prod" && + {env.STAGE !== 'prod' && ( + This is the {env.STAGE} version of postal-vote. - } + + )} - navigate('/is-registered')} /> - } /> - navigate('/country')} onNotRegistered={() => navigate('/needs-registration')} /> - } /> - - } /> - navigate('/postal-vote-form/your-details')} onNI={() => navigate('/northern-ireland')} /> - } /> - - } /> - - } /> + navigate('/is-registered')} /> + } + /> + navigate('/country')} onNotRegistered={() => navigate('/needs-registration')} /> + } + /> + + } + /> + navigate('/postal-vote-form/your-details')} onNI={() => navigate('/northern-ireland')} /> + } + /> + + } + /> + + } + /> @@ -64,7 +92,7 @@ const App = () => {
Built by Adam Jones with source code available on GitHub} />
- ) -} + ); +}; export default App; diff --git a/web/src/components/rhfDateField.tsx b/web/src/components/rhfDateField.tsx index be11545..0135284 100644 --- a/web/src/components/rhfDateField.tsx +++ b/web/src/components/rhfDateField.tsx @@ -1,6 +1,6 @@ -import React from "react"; -import { DateField } from 'govuk-react' -import { DateFieldProps } from "@govuk-react/date-field"; +import React from 'react'; +import { DateField } from 'govuk-react'; +import type { DateFieldProps } from '@govuk-react/date-field'; const RHFDateField: React.FC<{ input: { @@ -41,13 +41,13 @@ const RHFDateField: React.FC<{ setValue({ ...value, ...newValue }); onChange({ target: { value: { ...value, ...newValue }, name: input.name } }); }, - onBlur: (newValue) => onBlur({ target: { value, name: input.name } }), + onBlur: () => onBlur({ target: { value, name: input.name } }), ...input, }} > {children} - + ); }; -export default RHFDateField; \ No newline at end of file +export default RHFDateField; diff --git a/web/src/env/dev.ts b/web/src/env/dev.ts index be58d6b..bd18e38 100644 --- a/web/src/env/dev.ts +++ b/web/src/env/dev.ts @@ -1,11 +1,11 @@ -import type { Env } from "../types" +import type { Env } from '../types'; const env: Env = { - STAGE: "dev", + STAGE: 'dev', - API_BASE_URL: "https://1vti3u2qk1.execute-api.eu-west-1.amazonaws.com", + API_BASE_URL: 'https://1vti3u2qk1.execute-api.eu-west-1.amazonaws.com', - RECAPTCHA_V3_SITE_KEY: "6LfZqq0fAAAAAIxbz8FaOa4SKQeefeISd6TCN6AN", -} + RECAPTCHA_V3_SITE_KEY: '6LfZqq0fAAAAAIxbz8FaOa4SKQeefeISd6TCN6AN', +}; -export default env +export default env; diff --git a/web/src/env/local.template.ts b/web/src/env/local.template.ts index 345c943..2c5e763 100644 --- a/web/src/env/local.template.ts +++ b/web/src/env/local.template.ts @@ -1,11 +1,11 @@ -import type { Env } from "../types" +import type { Env } from '../types'; const env: Env = { - STAGE: "local", + STAGE: 'local', - API_BASE_URL: "http://localhost:8001", + API_BASE_URL: 'http://localhost:8001', - RECAPTCHA_V3_SITE_KEY: "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI", -} + RECAPTCHA_V3_SITE_KEY: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', +}; -export default env +export default env; diff --git a/web/src/env/prod.ts b/web/src/env/prod.ts index d8d4695..a81e400 100644 --- a/web/src/env/prod.ts +++ b/web/src/env/prod.ts @@ -1,11 +1,11 @@ -import type { Env } from "../types" +import type { Env } from '../types'; const env: Env = { - STAGE: "prod", + STAGE: 'prod', - API_BASE_URL: "https://6j0yoseuwi.execute-api.eu-west-1.amazonaws.com", + API_BASE_URL: 'https://6j0yoseuwi.execute-api.eu-west-1.amazonaws.com', - RECAPTCHA_V3_SITE_KEY: "6LdLmK0fAAAAAFiQ8edy697yN4jhpl1K33I8Bv-M", -} + RECAPTCHA_V3_SITE_KEY: '6LdLmK0fAAAAAFiQ8edy697yN4jhpl1K33I8Bv-M', +}; -export default env +export default env; diff --git a/web/src/index.tsx b/web/src/index.tsx index 0cb3ee4..cb918ec 100644 --- a/web/src/index.tsx +++ b/web/src/index.tsx @@ -5,7 +5,7 @@ import App from './App'; import env from './env/env'; const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById('root') as HTMLElement, ); root.render( @@ -13,10 +13,10 @@ root.render( - + , ); -const recaptchaScript = document.createElement('script') -recaptchaScript.src = `https://www.google.com/recaptcha/api.js?render=${env.RECAPTCHA_V3_SITE_KEY}` -recaptchaScript.async = true -document.body.appendChild(recaptchaScript) +const recaptchaScript = document.createElement('script'); +recaptchaScript.src = `https://www.google.com/recaptcha/api.js?render=${env.RECAPTCHA_V3_SITE_KEY}`; +recaptchaScript.async = true; +document.body.appendChild(recaptchaScript); diff --git a/web/src/pages/CountryForm.tsx b/web/src/pages/CountryForm.tsx index 19c4db2..d991b32 100644 --- a/web/src/pages/CountryForm.tsx +++ b/web/src/pages/CountryForm.tsx @@ -1,10 +1,12 @@ -import { Button, Fieldset, MultiChoice, Radio } from "govuk-react"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { + Button, Fieldset, MultiChoice, Radio, +} from 'govuk-react'; +import { SubmitHandler, useForm } from 'react-hook-form'; const CountryForm = ({ onNonNI, onNI }: { onNonNI: () => void, onNI: () => void }) => { type TFieldValues = { country: 'England' | 'Scotland' | 'Wales' | 'Northern Ireland' - } + }; const { register, @@ -12,14 +14,14 @@ const CountryForm = ({ onNonNI, onNI }: { onNonNI: () => void, onNI: () => void formState: { errors, submitCount, isSubmitting }, } = useForm({ reValidateMode: 'onSubmit', - }) + }); - const validateAnswered = (value?: string): string | undefined => value?.length ? undefined : 'Please select an option'; + const validateAnswered = (value?: string): string | undefined => (value?.length ? undefined : 'Please select an option'); const onSubmit: SubmitHandler = ({ country }) => { - if (country === 'Northern Ireland') return onNI() - return onNonNI() - } + if (country === 'Northern Ireland') return onNI(); + return onNonNI(); + }; return (
@@ -71,7 +73,7 @@ const CountryForm = ({ onNonNI, onNI }: { onNonNI: () => void, onNI: () => void
- ) -} + ); +}; -export default CountryForm \ No newline at end of file +export default CountryForm; diff --git a/web/src/pages/IsRegisteredForm.tsx b/web/src/pages/IsRegisteredForm.tsx index 7fa1ba2..d1bbafa 100644 --- a/web/src/pages/IsRegisteredForm.tsx +++ b/web/src/pages/IsRegisteredForm.tsx @@ -1,10 +1,12 @@ -import { Button, Fieldset, MultiChoice, Radio } from "govuk-react"; -import { SubmitHandler, useForm } from "react-hook-form"; +import { + Button, Fieldset, MultiChoice, Radio, +} from 'govuk-react'; +import { SubmitHandler, useForm } from 'react-hook-form'; const IsRegisteredForm = ({ onRegistered, onNotRegistered }: { onRegistered: () => void, onNotRegistered: () => void }) => { type TFieldValues = { isRegistered: 'yes' | 'no' - } + }; const { register, @@ -12,15 +14,15 @@ const IsRegisteredForm = ({ onRegistered, onNotRegistered }: { onRegistered: () formState: { errors, submitCount, isSubmitting }, } = useForm({ reValidateMode: 'onSubmit', - }) + }); - const validateAnswered = (value?: string): string | undefined => value?.length ? undefined : 'Please select an option'; + const validateAnswered = (value?: string): string | undefined => (value?.length ? undefined : 'Please select an option'); const onSubmit: SubmitHandler = ({ isRegistered }) => { - if (isRegistered === 'yes') return onRegistered() - if (isRegistered === 'no') return onNotRegistered() - throw new Error('Invalid value for isRegistered field: ' + isRegistered) - } + if (isRegistered === 'yes') return onRegistered(); + if (isRegistered === 'no') return onNotRegistered(); + throw new Error(`Invalid value for isRegistered field: ${isRegistered}`); + }; return (
@@ -65,7 +67,7 @@ const IsRegisteredForm = ({ onRegistered, onNotRegistered }: { onRegistered: ()
- ) -} + ); +}; -export default IsRegisteredForm \ No newline at end of file +export default IsRegisteredForm; diff --git a/web/src/pages/NeedsRegistration.tsx b/web/src/pages/NeedsRegistration.tsx index 9e91bce..2d99677 100644 --- a/web/src/pages/NeedsRegistration.tsx +++ b/web/src/pages/NeedsRegistration.tsx @@ -1,4 +1,4 @@ -import { Button, Heading, Paragraph } from "govuk-react" +import { Button, Heading, Paragraph } from 'govuk-react'; const NeedsRegistration = () => { return ( @@ -7,11 +7,11 @@ const NeedsRegistration = () => { You must register before applying for a postal vote. The deadline to register to vote is midnight, 12 working days before the poll. If you're not sure if you are registered to vote, you should register again. You can come back to this service once you have registered to vote. - - ) -} + ); +}; -export default NeedsRegistration \ No newline at end of file +export default NeedsRegistration; diff --git a/web/src/pages/NorthernIreland.tsx b/web/src/pages/NorthernIreland.tsx index 4b33b8b..4f8a73f 100644 --- a/web/src/pages/NorthernIreland.tsx +++ b/web/src/pages/NorthernIreland.tsx @@ -1,4 +1,4 @@ -import { Button, Heading, Paragraph } from "govuk-react" +import { Button, Heading, Paragraph } from 'govuk-react'; const NorthernIreland = () => { return ( @@ -6,11 +6,11 @@ const NorthernIreland = () => { You need to contact EONI The Electoral Office for Northern Ireland (EONI) handles postal vote applications for voters in Northern Ireland. The EONI website has more details on eligibility criteria and how to apply. - - ) -} + ); +}; -export default NorthernIreland \ No newline at end of file +export default NorthernIreland; diff --git a/web/src/pages/PostalVoteForm.tsx b/web/src/pages/PostalVoteForm.tsx index e06cdf8..cade4c7 100644 --- a/web/src/pages/PostalVoteForm.tsx +++ b/web/src/pages/PostalVoteForm.tsx @@ -1,10 +1,12 @@ -import React, { useEffect } from "react" -import { Button, Checkbox, Details, ErrorText, Fieldset, FormGroup, Heading, HintText, InputField, LabelText, Link, Panel, Paragraph, Select } from "govuk-react" -import { SubmitHandler, useForm, UseFormReturn } from "react-hook-form" -import RHFDateField from "../components/rhfDateField" -import SignaturePadLib from "signature_pad" -import { Route, Routes, useNavigate } from "react-router-dom" -import env from "../env/env" +import React, { useEffect } from 'react'; +import { + Button, Checkbox, Details, ErrorText, Fieldset, FormGroup, Heading, HintText, InputField, LabelText, Link, Panel, Paragraph, Select, +} from 'govuk-react'; +import { SubmitHandler, useForm, UseFormReturn } from 'react-hook-form'; +import SignaturePadLib from 'signature_pad'; +import { Route, Routes, useNavigate } from 'react-router-dom'; +import RHFDateField from '../components/rhfDateField'; +import env from '../env/env'; type TFieldValues = { firstName: string, @@ -25,7 +27,7 @@ type TFieldValues = { alternativeAddressReasonOther: string, signatureProvided: boolean, signatureDataUri: string, -} +}; interface Request { firstName: string, @@ -47,17 +49,17 @@ interface Request { recaptchaToken?: string, } -const PAGE_1_FIELDS = ['firstName', 'lastName', 'dob', 'email', 'phone', 'addressLine1', 'addressLine2', 'addressLine3', 'addressPostcode', 'useAlternativeAddress', 'alternativeAddressLine1', 'alternativeAddressLine2', 'alternativeAddressLine3', 'alternativeAddressPostcode', 'alternativeAddressReason', 'alternativeAddressReasonOther'] as const +const PAGE_1_FIELDS = ['firstName', 'lastName', 'dob', 'email', 'phone', 'addressLine1', 'addressLine2', 'addressLine3', 'addressPostcode', 'useAlternativeAddress', 'alternativeAddressLine1', 'alternativeAddressLine2', 'alternativeAddressLine3', 'alternativeAddressPostcode', 'alternativeAddressReason', 'alternativeAddressReasonOther'] as const; const postcodeRegex = /^([A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]?[0-9][ABD-HJLN-UW-Z]{2}|GIR0AA)$/i; -const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ +const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; const PostalVoteForm = () => { - const form = useForm({ reValidateMode: 'onSubmit' }) - const navigate = useNavigate() + const form = useForm({ reValidateMode: 'onSubmit' }); + const navigate = useNavigate(); - const [councilName, setCouncilName] = React.useState(null) - const [pdf, setPdf] = React.useState(null) + const [councilName, setCouncilName] = React.useState(null); + const [pdf, setPdf] = React.useState(null); const onSubmit: SubmitHandler = async (data) => { const dob = String(Number(data.dob.day)).padStart(2, '0') + String(Number(data.dob.month)).padStart(2, '0') + String(Number(data.dob.year)); @@ -83,73 +85,88 @@ const PostalVoteForm = () => { signatureDataUri: data.signatureDataUri, date, recaptchaToken: await getRecaptchaToken(), - } + }; - const submitResponse = await fetch(env.API_BASE_URL + '/submit', { + const submitResponse = await fetch(`${env.API_BASE_URL}/submit`, { method: 'POST', headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' + Accept: 'application/json', + 'Content-Type': 'application/json', }, - body: JSON.stringify(request) + body: JSON.stringify(request), }); const content: { message: string, councilName?: string, pdf?: string } = await submitResponse.json(); if (submitResponse.status === 200) { if (content.councilName) { - setCouncilName(content.councilName) + setCouncilName(content.councilName); } - navigate('/postal-vote-form/success') + navigate('/postal-vote-form/success'); } else if (submitResponse.status === 404 && content.pdf) { - setPdf(content.pdf) - navigate('/postal-vote-form/manual') + setPdf(content.pdf); + navigate('/postal-vote-form/manual'); } else { - alert(`Error: status ${submitResponse.status} from API, with body: ${JSON.stringify(content)}.`) - console.error(submitResponse) + alert(`Error: status ${submitResponse.status} from API, with body: ${JSON.stringify(content)}.`); + console.error(submitResponse); } - } + }; return (
- - } /> - - } /> - - } /> - {pdf && - } />} + + } + /> + + } + /> + + } + /> + {pdf && ( + + } + /> + )}
- ) -} + ); +}; const getRecaptchaToken = (): Promise => { return new Promise((resolve) => { if ('grecaptcha' in window) { - const grecaptcha = (window as any).grecaptcha; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { grecaptcha } = (window as any); grecaptcha.ready(() => { grecaptcha.execute(env.RECAPTCHA_V3_SITE_KEY, { action: 'submit' }).then((token: string | null) => { - resolve(token ?? undefined) + resolve(token ?? undefined); }); }); } else { - resolve(undefined) + resolve(undefined); } - }) -} + }); +}; const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => { - const navigate = useNavigate() + const navigate = useNavigate(); - const validateFirstName = (value?: string): string | undefined => value?.length ? undefined : 'Please enter your first name'; - const validateLastName = (value?: string): string | undefined => value?.length ? undefined : 'Please enter your last name'; + const validateFirstName = (value?: string): string | undefined => (value?.length ? undefined : 'Please enter your first name'); + const validateLastName = (value?: string): string | undefined => (value?.length ? undefined : 'Please enter your last name'); const validateDOB: (value?: { year?: number | string; month?: number | string; @@ -162,13 +179,13 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => const testDate = new Date(year, month, day); if ( // Check date is in the past - testDate < new Date() && + testDate < new Date() // Is after 1900 - testDate.getFullYear() > 1900 && + && testDate.getFullYear() > 1900 // and a real date resolves to the inputted date (e.g. month is not 13, not 29th February on a non leap year) - testDate.getFullYear() === year && - testDate.getMonth() === month && - testDate.getDate() === day + && testDate.getFullYear() === year + && testDate.getMonth() === month + && testDate.getDate() === day ) { return undefined; } @@ -176,17 +193,18 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => } return 'Please enter your date of birth'; }; - const validateAddressLine1 = (value?: string): string | undefined => value?.length ? undefined : 'Please enter your address'; + const validateAddressLine1 = (value?: string): string | undefined => (value?.length ? undefined : 'Please enter your address'); const validatePostcode = (value?: string): string | undefined => { if (value === undefined || value.length === 0) return 'Please enter your postcode'; if (!postcodeRegex.test(value.replace(/\s/g, ''))) return 'Please enter a valid postcode'; - } + return undefined; + }; const validateEmail = (value?: string): string | undefined => { if (value === undefined || value.length === 0) return 'Please enter your email'; if (!emailRegex.test(value.trim())) return 'Please enter a valid email'; - return - } - const validateAlternativeAddressReasonOther = (value?: string): string | undefined => value?.length ? undefined : 'Please enter a reason'; + return undefined; + }; + const validateAlternativeAddressReasonOther = (value?: string): string | undefined => (value?.length ? undefined : 'Please enter a reason'); return (
@@ -234,7 +252,6 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => Phone number (optional) - Enter the address where you are registered to vote. }) => mb={4} meta={{ error: form.formState.errors.addressLine3?.message, touched: true }} input={{ autoComplete: 'address-level2', ...form.register('addressLine3') }} - // @ts-ignore style={{ width: 'calc(max(66%, 16rem))' }} > Town or city (optional) @@ -263,7 +279,6 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => mb={4} meta={{ error: form.formState.errors.addressPostcode?.message, touched: true }} input={{ autoComplete: 'postal-code', ...form.register('addressPostcode', { validate: validatePostcode }) }} - // @ts-ignore style={{ width: '16rem' }} > Postcode @@ -276,17 +291,20 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => Send my ballot paper to a different address - {form.watch('useAlternativeAddress', false) && <> + {form.watch('useAlternativeAddress', false) && ( + <> Enter the address where you want your ballot paper sent. { - if (form.watch('useAlternativeAddress', false)) return validateAddressLine1(s) - } - }) + if (form.watch('useAlternativeAddress', false)) return validateAddressLine1(s); + return undefined; + }, + }), }} > Address line 1 @@ -302,7 +320,6 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => mb={4} meta={{ error: form.formState.errors.alternativeAddressLine3?.message, touched: true }} input={{ autoComplete: 'address-level2', ...form.register('alternativeAddressLine3') }} - // @ts-ignore style={{ width: 'calc(max(66%, 16rem))' }} > Town or city (optional) @@ -311,13 +328,14 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => mb={4} meta={{ error: form.formState.errors.alternativeAddressPostcode?.message, touched: true }} input={{ - autoComplete: 'postal-code', ...form.register('alternativeAddressPostcode', { + autoComplete: 'postal-code', + ...form.register('alternativeAddressPostcode', { validate: (s) => { - if (form.watch('useAlternativeAddress', false)) return validatePostcode(s) - } - }) + if (form.watch('useAlternativeAddress', false)) return validatePostcode(s); + return undefined; + }, + }), }} - // @ts-ignore style={{ width: '16rem' }} > Postcode @@ -336,104 +354,108 @@ const PostalVoteFormPage1 = ({ form }: { form: UseFormReturn }) => - {form.watch('alternativeAddressReason', '') === 'Other' && <> - { - if (form.watch('useAlternativeAddress', false) && form.watch('alternativeAddressReason', '') === 'Other') return validateAlternativeAddressReasonOther(s) - } - })} - > - Your other reason - - } - } + {form.watch('alternativeAddressReason', '') === 'Other' && ( + { + if (form.watch('useAlternativeAddress', false) && form.watch('alternativeAddressReason', '') === 'Other') return validateAlternativeAddressReasonOther(s); + return undefined; + }, + })} + > + Your other reason + + )} + + )}
- ) -} + ); +}; const PostalVoteFormPage2 = ({ form }: { form: UseFormReturn }) => { - const navigate = useNavigate() - const [pad, setPad] = React.useState(null) + const navigate = useNavigate(); + const [pad, setPad] = React.useState(null); // Check we haven't jumped into the middle of the flow useEffect(() => { // By this point we should have a first name // If we don't, it probably means we jumped into the middle of the flow if (form.watch('firstName') === undefined) { - console.error('User appears to have started mid-flow... redirecting to earlier in flow') - navigate('/postal-vote-form/your-details') + console.error('User appears to have started mid-flow... redirecting to earlier in flow'); + navigate('/postal-vote-form/your-details'); } - }, [form, navigate]) - + }, [form, navigate]); // Register virtual form fields useEffect(() => { form.register('signatureProvided', { validate: (value) => { if (!value) { - if (navigator.maxTouchPoints > 0) return "Please provide your signature" - return "Please provide your signature. Hold down your left mouse button while moving your mouse in the box below to draw your signature." - } - } - }) - }, [form]) - - return (
- - Declaration - - Declaration: As far as I know, the details on this form are true and accurate. - I understand that to provide false information on this form is an offence, punishable on conviction by imprisonment of up to two years and/or a fine. - - form.setValue('signatureProvided', touched)} - /> - -
-

If you can't provide a signature or consistent signature due to a disability or inability to read or write, you should contact your local electoral registration office.

- -

The Electoral Commision website has additional guidance about signature waivers for postal votes.

-
- - + return ( +
+ + Declaration + + Declaration: As far as I know, the details on this form are true and accurate. + I understand that to provide false information on this form is an offence, punishable on conviction by imprisonment of up to two years and/or a fine. - This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply. -
) -} + form.setValue('signatureProvided', touched)} + /> + +
+

If you can't provide a signature or consistent signature due to a disability or inability to read or write, you should contact your local electoral registration office.

+ +

The Electoral Commision website has additional guidance about signature waivers for postal votes.

+
+ + + + This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply. +
+ ); +}; const Success = ({ councilName }: { councilName: string | null }) => { return ( @@ -444,32 +466,34 @@ const Success = ({ councilName }: { councilName: string | null }) => { {`We’ve sent your application to ${councilName ?? 'your local'} electoral register office.`} They will contact you either to confirm your postal vote, or to ask for more information. - ) -} + ); +}; const Manual = ({ pdf, postcode }: { pdf: string, postcode: string }) => { - const navigate = useNavigate() + const navigate = useNavigate(); // Check we haven't jumped into the middle of the flow useEffect(() => { // By this point we should have a pdf and postcode // If we don't, it probably means we jumped into the middle of the flow if (!pdf || !postcode) { - console.error('User appears to have started mid-flow... redirecting to earlier in flow') - navigate('/postal-vote-form/your-details') + console.error('User appears to have started mid-flow... redirecting to earlier in flow'); + navigate('/postal-vote-form/your-details'); } - }, [pdf, postcode, navigate]) + }, [pdf, postcode, navigate]); return ( <> Sending your form We've prepared your postal vote application form. Download it and email or post it to your local electoral registration office. - {children}}>{`You can find their details on the [GOV.UK website](https://www.gov.uk/contact-electoral-registration-office?postcode=${encodeURIComponent(postcode)}).`} + {`You can find their details on the [GOV.UK website](https://www.gov.uk/contact-electoral-registration-office?postcode=${encodeURIComponent(postcode)}).`} They will contact you either to confirm your postal vote, or to ask for more information. - + - ) -} + ); +}; + +const LinkRenderer: React.FC> = ({ href, children }) => {children}; const SignaturePad = ({ onLoad, meta, onTouchedChanged }: { onLoad: (s: SignaturePadLib) => void, meta: { error?: string }, onTouchedChanged: (touched: boolean) => void }) => { const canvasRef = React.useRef(null); @@ -478,63 +502,65 @@ const SignaturePad = ({ onLoad, meta, onTouchedChanged }: { onLoad: (s: Signatur React.useEffect(() => { if (canvasRef.current != null) { - const canvas = canvasRef.current + const canvas = canvasRef.current; const s = new SignaturePadLib(canvas, { minDistance: 1 }); - s.on() + s.on(); setPad(s); - return () => s.off() + return () => s.off(); } - }, []) + return undefined; + }, []); React.useEffect(() => { if (pad && canvasRef.current) { - const canvas = canvasRef.current + const canvas = canvasRef.current; - onLoad(pad) + onLoad(pad); const onBeginStroke = () => { - setTouched(true) - onTouchedChanged(true) - } - pad.addEventListener('beginStroke', onBeginStroke) + setTouched(true); + onTouchedChanged(true); + }; + pad.addEventListener('beginStroke', onBeginStroke); const resizeCanvas = () => { // Canvas not actually changing size if (canvas.width === canvas.offsetWidth) { - return + return; } // This part causes the canvas to be cleared canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetWidth / 3.24324324; - canvas.getContext("2d")?.scale(1, 1); + canvas.getContext('2d')?.scale(1, 1); // Resizing the canvas clears it. To make the state of the SignaturePadLib // consistent with the canvas, we clear it manually. pad.clear(); - setTouched(false) - onTouchedChanged(false) - } + setTouched(false); + onTouchedChanged(false); + }; window.addEventListener('resize', resizeCanvas); resizeCanvas(); return () => { - pad.removeEventListener('beginStroke', onBeginStroke) - window.removeEventListener('resize', resizeCanvas) - } + pad.removeEventListener('beginStroke', onBeginStroke); + window.removeEventListener('resize', resizeCanvas); + }; } - }, [pad, onLoad, onTouchedChanged]) + return undefined; + }, [pad, onLoad, onTouchedChanged]); - return <> + return (
Signature - {navigator.maxTouchPoints > 0 ? "Touch" : "Click in"} and draw your signature in the box below + {navigator.maxTouchPoints > 0 ? 'Touch' : 'Click in'} and draw your signature in the box below {meta.error && {meta.error}}
+ marginRight: meta.error ? '0' : '20px', + }} + >
{ - e.preventDefault() + e.preventDefault(); if (pad) { - pad.clear() + pad.clear(); } - setTouched(false) - onTouchedChanged(false) + setTouched(false); + onTouchedChanged(false); }} > Clear signature
- -} + ); +}; -export default PostalVoteForm \ No newline at end of file +export default PostalVoteForm; diff --git a/web/src/pages/Start.test.tsx b/web/src/pages/Start.test.tsx index 38b15dc..bf5f94a 100644 --- a/web/src/pages/Start.test.tsx +++ b/web/src/pages/Start.test.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { render, screen } from '@testing-library/react'; -import Start from './Start'; import userEvent from '@testing-library/user-event'; +import Start from './Start'; test('renders apply for a postal vote text', () => { render( { }} />); @@ -9,18 +8,18 @@ test('renders apply for a postal vote text', () => { }); test('clicking button triggers onStart', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); // Given... start page with callback const onStart = jest.fn(); render(); // Then... callback not automatically called - expect(onStart).not.toHaveBeenCalled() + expect(onStart).not.toHaveBeenCalled(); // When... start button pressed - await user.click(screen.getByText(/Start now/i)) + await user.click(screen.getByText(/Start now/i)); // Then... onStart callback called - expect(onStart).toHaveBeenCalledTimes(1) + expect(onStart).toHaveBeenCalledTimes(1); }); diff --git a/web/src/pages/Start.tsx b/web/src/pages/Start.tsx index b0bc436..871cbd9 100644 --- a/web/src/pages/Start.tsx +++ b/web/src/pages/Start.tsx @@ -1,4 +1,6 @@ -import { Button, ButtonArrow, Heading, Paragraph } from "govuk-react"; +import { + Button, ButtonArrow, Heading, Paragraph, +} from 'govuk-react'; const Start = ({ onStart }: { onStart: () => void }) => ( <> @@ -12,6 +14,6 @@ const Start = ({ onStart }: { onStart: () => void }) => ( Start now -) +); -export default Start \ No newline at end of file +export default Start; diff --git a/web/src/react-app-env.d.ts b/web/src/react-app-env.d.ts index 3e99f13..22c53fb 100644 --- a/web/src/react-app-env.d.ts +++ b/web/src/react-app-env.d.ts @@ -1,6 +1,6 @@ /// declare module '*.pdf' { - const src: string; - export default src; + const src: string; + export default src; } diff --git a/web/src/types.ts b/web/src/types.ts index a900b93..1ede741 100644 --- a/web/src/types.ts +++ b/web/src/types.ts @@ -1,7 +1,7 @@ export interface Env { - STAGE: "local" | "dev" | "prod", + STAGE: 'local' | 'dev' | 'prod', - API_BASE_URL: string, + API_BASE_URL: string, - RECAPTCHA_V3_SITE_KEY: string, + RECAPTCHA_V3_SITE_KEY: string, }