diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts index 4f11a03dc..fd36f9494 100644 --- a/frontend/next-env.d.ts +++ b/frontend/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1932ea3d1..77e549aad 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,13 +10,14 @@ "hasInstallScript": true, "dependencies": { "@next/third-parties": "^14.0.4", - "@trussworks/react-uswds": "^5.0.0", + "@trussworks/react-uswds": "^7.0.0", "@uswds/uswds": "^3.6.0", "i18next": "^23.0.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", - "next": "^13.5.2", + "next": "^14.0.3", "next-i18next": "^15.0.0", + "next-intl": "^3.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^14.0.0", @@ -3486,6 +3487,92 @@ "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", "dev": true }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz", + "integrity": "sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz", + "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz", + "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "@formatjs/icu-skeleton-parser": "1.3.6", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz", + "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.2.32", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", + "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -4814,9 +4901,9 @@ } }, "node_modules/@next/env": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.6.tgz", - "integrity": "sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==" + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", + "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" }, "node_modules/@next/eslint-plugin-next": { "version": "13.5.6", @@ -4848,9 +4935,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.6.tgz", - "integrity": "sha512-5nvXMzKtZfvcu4BhtV0KH1oGv4XEW+B+jOfmBdpFI3C7FrB/MfujRpWYSBBO64+qbW8pkZiSyQv9eiwnn5VIQA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", + "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", "cpu": [ "arm64" ], @@ -4863,9 +4950,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.6.tgz", - "integrity": "sha512-6cgBfxg98oOCSr4BckWjLLgiVwlL3vlLj8hXg2b+nDgm4bC/qVXXLfpLB9FHdoDu4057hzywbxKvmYGmi7yUzA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", + "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", "cpu": [ "x64" ], @@ -4878,9 +4965,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.6.tgz", - "integrity": "sha512-txagBbj1e1w47YQjcKgSU4rRVQ7uF29YpnlHV5xuVUsgCUf2FmyfJ3CPjZUvpIeXCJAoMCFAoGnbtX86BK7+sg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", + "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", "cpu": [ "arm64" ], @@ -4893,9 +4980,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.6.tgz", - "integrity": "sha512-cGd+H8amifT86ZldVJtAKDxUqeFyLWW+v2NlBULnLAdWsiuuN8TuhVBt8ZNpCqcAuoruoSWynvMWixTFcroq+Q==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", + "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", "cpu": [ "arm64" ], @@ -4908,9 +4995,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.6.tgz", - "integrity": "sha512-Mc2b4xiIWKXIhBy2NBTwOxGD3nHLmq4keFk+d4/WL5fMsB8XdJRdtUlL87SqVCTSaf1BRuQQf1HvXZcy+rq3Nw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", + "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", "cpu": [ "x64" ], @@ -4923,9 +5010,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.6.tgz", - "integrity": "sha512-CFHvP9Qz98NruJiUnCe61O6GveKKHpJLloXbDSWRhqhkJdZD2zU5hG+gtVJR//tyW897izuHpM6Gtf6+sNgJPQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", + "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", "cpu": [ "x64" ], @@ -4938,9 +5025,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.6.tgz", - "integrity": "sha512-aFv1ejfkbS7PUa1qVPwzDHjQWQtknzAZWGTKYIAaS4NMtBlk3VyA6AYn593pqNanlicewqyl2jUhQAaFV/qXsg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", + "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", "cpu": [ "arm64" ], @@ -4953,9 +5040,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.6.tgz", - "integrity": "sha512-XqqpHgEIlBHvzwG8sp/JXMFkLAfGLqkbVsyN+/Ih1mR8INb6YCc2x/Mbwi6hsAgUnqQztz8cvEbHJUbSl7RHDg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", + "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", "cpu": [ "ia32" ], @@ -4968,9 +5055,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.6.tgz", - "integrity": "sha512-Cqfe1YmOS7k+5mGu92nl5ULkzpKuxJrP3+4AEuPmrpFZ3BHxTY3TnHmU1On3bFmFFs6FbTcdF58CCUProGpIGQ==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", + "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", "cpu": [ "x64" ], @@ -7645,14 +7732,14 @@ } }, "node_modules/@trussworks/react-uswds": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-5.5.0.tgz", - "integrity": "sha512-PZRklIYWIIXI+hTw2XILPHKictZXwnPaSZf9L1mRTmfj1IncktPPpCCigwfIxIG6MSMzZYXx/TOAwjb8n4sjhw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-7.0.0.tgz", + "integrity": "sha512-N3O0BRuhyLoZKjYM0Mq+XIXJ3ShvCEh6uhqwRstIde6E9m0Au6JLy1YEv9A8eapBxDvyqZW3jiVTGzW/JqS+eA==", "engines": { "node": ">= 16.20.0" }, "peerDependencies": { - "@uswds/uswds": "^3.6.0", + "@uswds/uswds": "^3.7.1", "react": "^16.x || ^17.x || ^18.x", "react-dom": "^16.x || ^17.x || ^18.x" } @@ -13966,7 +14053,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", @@ -14748,6 +14836,34 @@ "node": ">= 0.4" } }, + "node_modules/intl-messageformat": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz", + "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==", + "dependencies": { + "@formatjs/ecma402-abstract": "1.11.4", + "@formatjs/fast-memoize": "1.2.1", + "@formatjs/icu-messageformat-parser": "2.1.0", + "tslib": "^2.1.0" + } + }, + "node_modules/intl-messageformat/node_modules/@formatjs/ecma402-abstract": { + "version": "1.11.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz", + "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==", + "dependencies": { + "@formatjs/intl-localematcher": "0.2.25", + "tslib": "^2.1.0" + } + }, + "node_modules/intl-messageformat/node_modules/@formatjs/intl-localematcher": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz", + "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -18244,7 +18360,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -18256,34 +18371,34 @@ "dev": true }, "node_modules/next": { - "version": "13.5.6", - "resolved": "https://registry.npmjs.org/next/-/next-13.5.6.tgz", - "integrity": "sha512-Y2wTcTbO4WwEsVb4A8VSnOsG1I9ok+h74q0ZdxkwM3EODqrs4pasq7O0iUxbcS9VtWMicG7f3+HAj0r1+NtKSw==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", + "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", "dependencies": { - "@next/env": "13.5.6", + "@next/env": "14.1.0", "@swc/helpers": "0.5.2", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=16.14.0" + "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.5.6", - "@next/swc-darwin-x64": "13.5.6", - "@next/swc-linux-arm64-gnu": "13.5.6", - "@next/swc-linux-arm64-musl": "13.5.6", - "@next/swc-linux-x64-gnu": "13.5.6", - "@next/swc-linux-x64-musl": "13.5.6", - "@next/swc-win32-arm64-msvc": "13.5.6", - "@next/swc-win32-ia32-msvc": "13.5.6", - "@next/swc-win32-x64-msvc": "13.5.6" + "@next/swc-darwin-arm64": "14.1.0", + "@next/swc-darwin-x64": "14.1.0", + "@next/swc-linux-arm64-gnu": "14.1.0", + "@next/swc-linux-arm64-musl": "14.1.0", + "@next/swc-linux-x64-gnu": "14.1.0", + "@next/swc-linux-x64-musl": "14.1.0", + "@next/swc-win32-arm64-msvc": "14.1.0", + "@next/swc-win32-ia32-msvc": "14.1.0", + "@next/swc-win32-x64-msvc": "14.1.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -18335,6 +18450,26 @@ "react-i18next": ">= 13.5.0" } }, + "node_modules/next-intl": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.9.1.tgz", + "integrity": "sha512-1j+5lLTY5kHshqnVoeAep+gQO1xX1KlJU3irZkdCOhr0woo562qLKOsGGVh+7bB5luBMu9qQBQy7ZwNK81Z2Ig==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "dependencies": { + "@formatjs/intl-localematcher": "^0.2.32", + "negotiator": "^0.6.3", + "use-intl": "^3.9.1" + }, + "peerDependencies": { + "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -23540,6 +23675,18 @@ } } }, + "node_modules/use-intl": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.9.1.tgz", + "integrity": "sha512-tWcT636/jYC0hILyFTLmiuE+ovbvPPBXwN/OOQxxE+4bssHXeeksdWXzpDsKqE37aV62DFrQ2jhumV8udecjNA==", + "dependencies": { + "@formatjs/ecma402-abstract": "^1.11.4", + "intl-messageformat": "^9.3.18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-resize-observer": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", @@ -23694,6 +23841,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" diff --git a/frontend/package.json b/frontend/package.json index 9e7c73b63..f6b30937e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,13 +23,14 @@ }, "dependencies": { "@next/third-parties": "^14.0.4", - "@trussworks/react-uswds": "^5.0.0", + "@trussworks/react-uswds": "^7.0.0", "@uswds/uswds": "^3.6.0", "i18next": "^23.0.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", - "next": "^13.5.2", + "next": "^14.0.3", "next-i18next": "^15.0.0", + "next-intl": "^3.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^14.0.0", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 000000000..ea54f234b --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,35 @@ +import "src/styles/styles.scss"; + +import Layout from "src/components/AppLayout"; +/** + * Root layout component, wraps all pages. + * @see https://nextjs.org/docs/app/api-reference/file-conventions/layout + */ +import { Metadata } from "next"; + +export const metadata: Metadata = { + icons: [`${process.env.NEXT_PUBLIC_BASE_PATH ?? ""}/img/favicon.ico`], +}; + +interface LayoutProps { + children: React.ReactNode; + + // TODO: use for i18n when ready + // params: { + // locale: string; + // }; +} + +export default function RootLayout({ children }: LayoutProps) { + return ( + + + {/* Separate layout component for the inner-body UI elements since Storybook + and tests trip over the fact that this file renders an tag */} + + {/* TODO: Add locale="english" prop when ready for i18n */} + {children} + + + ); +} diff --git a/frontend/src/app/search/page.tsx b/frontend/src/app/search/page.tsx new file mode 100644 index 000000000..7b99d4573 --- /dev/null +++ b/frontend/src/app/search/page.tsx @@ -0,0 +1,60 @@ +"use client"; + +import React, { useState } from "react"; +import { + SearchFetcher, + fetchSearchOpportunities, +} from "../../services/searchfetcher/SearchFetcher"; + +import { APISearchFetcher } from "../../services/searchfetcher/APISearchFetcher"; +import { MockSearchFetcher } from "../../services/searchfetcher/MockSearchFetcher"; +import { Opportunity } from "../../types/searchTypes"; +import PageNotFound from "../../pages/404"; +import { useFeatureFlags } from "src/hooks/useFeatureFlags"; + +const useMockData = true; +const searchFetcher: SearchFetcher = useMockData + ? new MockSearchFetcher() + : new APISearchFetcher(); + +// TODO: use for i18n when ready +// interface RouteParams { +// locale: string; +// } + +// interface SearchProps { +// initialOpportunities: Opportunity[]; +// } + +export default function Search() { + const { featureFlagsManager, mounted } = useFeatureFlags(); + const [searchResults, setSearchResults] = useState([]); + + const handleButtonClick = (event: React.MouseEvent) => { + event.preventDefault(); + performSearch().catch((e) => console.log(e)); + }; + + const performSearch = async () => { + const opportunities = await fetchSearchOpportunities(searchFetcher); + setSearchResults(opportunities); + }; + + if (!mounted) return null; + if (!featureFlagsManager.isFeatureEnabled("showSearchV0")) { + return ; + } + + return ( + <> + +
    + {searchResults.map((opportunity) => ( +
  • + {opportunity.id}, {opportunity.title} +
  • + ))} +
+ + ); +} diff --git a/frontend/src/components/AppLayout.tsx b/frontend/src/components/AppLayout.tsx new file mode 100644 index 000000000..0096ec643 --- /dev/null +++ b/frontend/src/components/AppLayout.tsx @@ -0,0 +1,26 @@ +import Footer from "./Footer"; +import GrantsIdentifier from "./GrantsIdentifier"; +import Header from "./Header"; + +type Props = { + children: React.ReactNode; + // TODO: pass locale into Layout when we setup i18n + // locale?: string; +}; + +const Layout = ({ children }: Props) => { + return ( + // Stick the footer to the bottom of the page +
+ + {"skip_to_main"} + +
+
{children}
+
+ +
+ ); +}; + +export default Layout; diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index 3adf13bee..c4cb7e163 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -1,3 +1,5 @@ +"use client"; + import { ExternalRoutes } from "src/constants/routes"; import { assetPath } from "src/utils/assetPath"; diff --git a/frontend/src/components/GrantsIdentifier.tsx b/frontend/src/components/GrantsIdentifier.tsx index 75c015092..8fb07f04f 100644 --- a/frontend/src/components/GrantsIdentifier.tsx +++ b/frontend/src/components/GrantsIdentifier.tsx @@ -1,3 +1,5 @@ +"use client"; + import { ExternalRoutes } from "src/constants/routes"; import { Trans, useTranslation } from "next-i18next"; diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 01beeff41..28c6887fa 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -1,3 +1,5 @@ +"use client"; + import { assetPath } from "src/utils/assetPath"; import { useTranslation } from "next-i18next"; diff --git a/frontend/src/pages/search.tsx b/frontend/src/pages/search.tsx deleted file mode 100644 index f18f16f3e..000000000 --- a/frontend/src/pages/search.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type { GetStaticProps, NextPage } from "next"; -import { useFeatureFlags } from "src/hooks/useFeatureFlags"; - -import { serverSideTranslations } from "next-i18next/serverSideTranslations"; -import React, { useState } from "react"; - -import { APISearchFetcher } from "../services/searchfetcher/APISearchFetcher"; -import { MockSearchFetcher } from "../services/searchfetcher/MockSearchFetcher"; -import { - fetchSearchOpportunities, - SearchFetcher, -} from "../services/searchfetcher/SearchFetcher"; -import { Opportunity } from "../types/searchTypes"; -import PageNotFound from "./404"; - -const useMockData = true; -const searchFetcher: SearchFetcher = useMockData - ? new MockSearchFetcher() - : new APISearchFetcher(); - -interface SearchProps { - initialOpportunities: Opportunity[]; -} - -const Search: NextPage = ({ initialOpportunities = [] }) => { - const { featureFlagsManager, mounted } = useFeatureFlags(); - const [searchResults, setSearchResults] = - useState(initialOpportunities); - - const handleButtonClick = (event: React.MouseEvent) => { - event.preventDefault(); - performSearch().catch((e) => console.log(e)); - }; - - const performSearch = async () => { - const opportunities = await fetchSearchOpportunities(searchFetcher); - setSearchResults(opportunities); - }; - - if (!mounted) return null; - if (!featureFlagsManager.isFeatureEnabled("showSearchV0")) { - return ; - } - - return ( - <> - -
    - {searchResults.map((opportunity) => ( -
  • - {opportunity.id}, {opportunity.title} -
  • - ))} -
- - ); -}; - -export const getStaticProps: GetStaticProps = async ({ locale }) => { - // Always pre-render the initial search results - // TODO (1189): If the URL has query params - they will need to be included in the search here - const initialOpportunities: Opportunity[] = - await fetchSearchOpportunities(searchFetcher); - const translations = await serverSideTranslations(locale ?? "en"); - - return { - props: { - initialOpportunities, - ...translations, - }, - }; -}; - -export default Search; diff --git a/frontend/stories/pages/search.stories.tsx b/frontend/stories/pages/search.stories.tsx index 04ed8b08b..e278ce84e 100644 --- a/frontend/stories/pages/search.stories.tsx +++ b/frontend/stories/pages/search.stories.tsx @@ -1,5 +1,5 @@ import { Meta } from "@storybook/react"; -import Search from "src/pages/search"; +import Search from "../../src/app/search/page"; const meta: Meta = { title: "Pages/Search", diff --git a/frontend/tests/components/AppLayout.test.tsx b/frontend/tests/components/AppLayout.test.tsx new file mode 100644 index 000000000..0d7279217 --- /dev/null +++ b/frontend/tests/components/AppLayout.test.tsx @@ -0,0 +1,29 @@ +import { render, screen } from "@testing-library/react"; +import { axe } from "jest-axe"; + +import AppLayout from "src/components/AppLayout"; + +describe("AppLayout", () => { + it("renders children in main section", () => { + render( + +

child

+
, + ); + + const header = screen.getByRole("heading", { name: /child/i, level: 1 }); + + expect(header).toBeInTheDocument(); + }); + + it("passes accessibility scan", async () => { + const { container } = render( + +

child

+
, + ); + const results = await axe(container); + + expect(results).toHaveNoViolations(); + }); +}); diff --git a/frontend/tests/components/Header.test.tsx b/frontend/tests/components/Header.test.tsx index c902f8ddc..8e3abbc49 100644 --- a/frontend/tests/components/Header.test.tsx +++ b/frontend/tests/components/Header.test.tsx @@ -20,8 +20,7 @@ const props = { describe("Header", () => { it("toggles the mobile nav menu", async () => { render(
); - - const menuButton = screen.getByRole("button", { name: /menu/i }); + const menuButton = screen.getByTestId("navMenuButton"); expect(menuButton).toBeInTheDocument(); expect(screen.getByRole("link", { name: /home/i })).toHaveAttribute( diff --git a/frontend/tests/pages/search.test.tsx b/frontend/tests/pages/search.test.tsx index 19519de9d..c8ccd7d1d 100644 --- a/frontend/tests/pages/search.test.tsx +++ b/frontend/tests/pages/search.test.tsx @@ -1,9 +1,8 @@ -import { render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { axe } from "jest-axe"; import { useFeatureFlags } from "src/hooks/useFeatureFlags"; -import Search from "src/pages/search"; -import { MOCKOPPORTUNITIES } from "../../src/services/searchfetcher/MockSearchFetcher"; +import Search from "../../src/app/search/page"; jest.mock("src/hooks/useFeatureFlags"); @@ -21,28 +20,33 @@ const setFeatureFlag = (flag: string, value: boolean) => { describe("Search", () => { it("passes accessibility scan", async () => { setFeatureFlag("showSearchV0", true); - const { container } = render( - , - ); + const { container } = render(); const results = await waitFor(() => axe(container)); expect(results).toHaveNoViolations(); }); describe("Search feature flag", () => { - it("renders search results when feature flag is on", () => { + it("renders search results when feature flag is on", async () => { setFeatureFlag("showSearchV0", true); - render(); - expect(screen.getByText(/sunt aut/i)).toBeInTheDocument(); + render(); + fireEvent.click(screen.getByRole("button", { name: /update results/i })); + + await waitFor(() => { + expect(screen.getByText(/sunt aut/i)).toBeInTheDocument(); + }); }); - it("renders PageNotFound when feature flag is off", () => { + it("renders PageNotFound when feature flag is off", async () => { setFeatureFlag("showSearchV0", false); - render(); - expect( - screen.getByText( - /The page you have requested cannot be displayed because it does not exist, has been moved/i, - ), - ).toBeInTheDocument(); + render(); + + await waitFor(() => { + expect( + screen.getByText( + /The page you have requested cannot be displayed because it does not exist, has been moved/i, + ), + ).toBeInTheDocument(); + }); }); }); }); diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index d3dc67260..dc547387b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -24,8 +24,19 @@ "@next/third-parties/google": [ "./node_modules/@next/third-parties/dist/google" ] - } + }, + "plugins": [ + { + "name": "next" + } + ] }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "__mocks__/styleMock.js"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + "__mocks__/styleMock.js", + ".next/types/**/*.ts" + ], "exclude": ["node_modules"] }