diff --git a/package-lock.json b/package-lock.json index 85cec64..ebf34e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,20 +19,21 @@ "@fortawesome/fontawesome-svg-core": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", - "@ionic/react": "8.3.2", - "@ionic/react-router": "8.3.2", - "@tanstack/react-query": "5.59.9", - "@tanstack/react-query-devtools": "5.59.9", + "@ionic/react": "8.3.3", + "@ionic/react-router": "8.3.3", + "@tanstack/react-query": "5.59.15", + "@tanstack/react-query-devtools": "5.59.15", "axios": "1.7.7", "classnames": "2.5.1", "dayjs": "1.11.13", "formik": "2.4.6", - "i18next": "23.15.2", + "i18next": "23.16.1", "i18next-browser-languagedetector": "8.0.0", "lodash": "4.17.21", "react": "18.3.1", "react-dom": "18.3.1", - "react-i18next": "15.0.2", + "react-error-boundary": "4.1.2", + "react-i18next": "15.0.3", "react-router": "5.3.4", "react-router-dom": "5.3.4", "uuid": "10.0.0", @@ -41,32 +42,32 @@ "devDependencies": { "@capacitor/cli": "6.1.2", "@testing-library/dom": "10.4.0", - "@testing-library/jest-dom": "6.5.0", + "@testing-library/jest-dom": "6.6.2", "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", - "@types/lodash": "4.17.10", + "@types/lodash": "4.17.12", "@types/react": "18.3.11", "@types/react-dom": "18.3.1", "@types/react-router": "5.1.20", "@types/react-router-dom": "5.3.3", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.8.1", - "@typescript-eslint/parser": "8.8.1", + "@typescript-eslint/eslint-plugin": "8.10.0", + "@typescript-eslint/parser": "8.10.0", "@vitejs/plugin-legacy": "5.4.2", - "@vitejs/plugin-react": "4.3.2", - "@vitest/coverage-v8": "2.1.2", + "@vitejs/plugin-react": "4.3.3", + "@vitest/coverage-v8": "2.1.3", "cypress": "13.15.0", "eslint": "8.57.0", "eslint-plugin-react": "7.37.1", "eslint-plugin-react-hooks": "5.0.0", - "eslint-plugin-react-refresh": "0.4.12", + "eslint-plugin-react-refresh": "0.4.13", "jsdom": "25.0.1", - "msw": "2.4.10", - "sass": "1.79.5", - "terser": "5.34.1", + "msw": "2.4.11", + "sass": "1.80.3", + "terser": "5.36.0", "typescript": "5.5.4", - "vite": "5.4.8", - "vitest": "2.1.2" + "vite": "5.4.9", + "vitest": "2.1.3" } }, "node_modules/@adobe/css-tools": { @@ -2959,9 +2960,9 @@ } }, "node_modules/@ionic/core": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.2.tgz", - "integrity": "sha512-ptiDXnn4131eKpY862lv7c9xxjly7vi4O+WWCES78E+hXHvTEAundcA5F8eQyb0MFIFvCnOxreTZjRJJnHqPYw==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.3.3.tgz", + "integrity": "sha512-f2PXV0jFIFPdjP+NbmQ1mXqRQ4KWi0U0jdQd3wDYsJFWQLmWXhW7Yp/4lCDdl0ouMeZRB2phddqFct1c7H6PyA==", "dependencies": { "@stencil/core": "4.20.0", "ionicons": "^7.2.2", @@ -2969,11 +2970,11 @@ } }, "node_modules/@ionic/react": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.2.tgz", - "integrity": "sha512-LOM+CrVgcR5aDH4LzgahGTz9gE5fn8JnRw6nXLkXWeu+qfic/qbLiRnaqLW9GAmMX0vSHeZc72AJTeG9VB5xYQ==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.3.3.tgz", + "integrity": "sha512-BQVke+4QF1viPmwYFV/Bfseh4AhLnA0svP8UvKTP45plJ2KDXF/IbFVNn+FWtjByrqYU4PldUgF01+O4yPGiRw==", "dependencies": { - "@ionic/core": "8.3.2", + "@ionic/core": "8.3.3", "ionicons": "^7.0.0", "tslib": "*" }, @@ -2983,11 +2984,11 @@ } }, "node_modules/@ionic/react-router": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.3.2.tgz", - "integrity": "sha512-q2srTJulTvy2rYMbxLpHQqMyWB9Q9Ac/pwZNDyh2pEAVa2JyTJpFRCr9Ihh25eIwBUq9rHODUqPbT8fzq4ju3g==", + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@ionic/react-router/-/react-router-8.3.3.tgz", + "integrity": "sha512-hdGnOj+bu289OiyjmenizG7DmVCz5chofZuzHpSRuK/QZ3xEGuCZrN/POgCBRjYCT4G9IzDWEMUGiog0UMM6Aw==", "dependencies": { - "@ionic/react": "8.3.2", + "@ionic/react": "8.3.3", "tslib": "*" }, "peerDependencies": { @@ -3940,9 +3941,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.9.tgz", - "integrity": "sha512-vFGnblfJOKlOPyTR5M0ohWKb/03eGubh5KuGyzsDfc7VQ6F0nsB75kQIoLpwp3Wfj6fKv0wGoTUX8BsIfhxDfw==", + "version": "5.59.13", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.13.tgz", + "integrity": "sha512-Oou0bBu/P8+oYjXsJQ11j+gcpLAMpqW42UlokQYEz4dE7+hOtVO9rVuolJKgEccqzvyFzqX4/zZWY+R/v1wVsQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -3958,11 +3959,11 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.9.tgz", - "integrity": "sha512-g2cbiw/ZIIrnUaQqhGtarTAsuLdKDNLtY5HNfRHVWY9kHDj96M4qs4ygJxHc119tPQpzZe4i9W7d2Gc2Gvng2A==", + "version": "5.59.15", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.15.tgz", + "integrity": "sha512-QbVlAkTI78wB4Mqgf2RDmgC0AOiJqer2c5k9STOOSXGv1S6ZkY37r/6UpE8DbQ2Du0ohsdoXgFNEyv+4eDoPEw==", "dependencies": { - "@tanstack/query-core": "5.59.9" + "@tanstack/query-core": "5.59.13" }, "funding": { "type": "github", @@ -3973,9 +3974,9 @@ } }, "node_modules/@tanstack/react-query-devtools": { - "version": "5.59.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.9.tgz", - "integrity": "sha512-Vfr8JPgx4GxopQOqdQTC7MAUbX1vuEqeexCIX0RiwjUmNCoHKUg2Mh3rTZPsx8Y7wscc7eWkBjiz03Dt/YM3oQ==", + "version": "5.59.15", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.15.tgz", + "integrity": "sha512-rX28KTivkA2XEn3Fj9ckDtnTPY8giWYgssySSAperpVol4+th+NCij/MhLylfB+Mfg2JfCxOcwnM/fwzS8iSog==", "dependencies": { "@tanstack/query-devtools": "5.58.0" }, @@ -3984,7 +3985,7 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@tanstack/react-query": "^5.59.9", + "@tanstack/react-query": "^5.59.15", "react": "^18 || ^19" } }, @@ -4008,9 +4009,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", - "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.2.tgz", + "integrity": "sha512-P6GJD4yqc9jZLbe98j/EkyQDTPgqftohZF5FBkHY5BUERZmcf4HeO2k0XaefEg329ux2p21i1A1DmyQ1kKw2Jw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.4.0", @@ -4247,9 +4248,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==", + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.12.tgz", + "integrity": "sha512-sviUmCE8AYdaF/KIHLDJBQgeYzPBI0vf/17NaYehBJfYD1j6/L95Slh07NlyK2iNyBNaEkb3En2jRt+a8y3xZQ==", "dev": true }, "node_modules/@types/minimist": { @@ -4375,16 +4376,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", - "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.10.0.tgz", + "integrity": "sha512-phuB3hoP7FFKbRXxjl+DRlQDuJqhpOnm5MmtROXyWi3uS/Xg2ZXqiQfcG2BJHiN4QKyzdOJi3NEn/qTnjUlkmQ==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/type-utils": "8.8.1", - "@typescript-eslint/utils": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/type-utils": "8.10.0", + "@typescript-eslint/utils": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -4408,15 +4409,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", - "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.10.0.tgz", + "integrity": "sha512-E24l90SxuJhytWJ0pTQydFT46Nk0Z+bsLKo/L8rtQSL93rQ6byd1V/QbDpHUTdLPOMsBCcYXZweADNCfOCmOAg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/typescript-estree": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "debug": "^4.3.4" }, "engines": { @@ -4436,13 +4437,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.10.0.tgz", + "integrity": "sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4453,13 +4454,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", - "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.10.0.tgz", + "integrity": "sha512-PCpUOpyQSpxBn230yIcK+LeCQaXuxrgCm2Zk1S+PTIRJsEfU6nJ0TtwyH8pIwPK/vJoA+7TZtzyAJSGBz+s/dg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/typescript-estree": "8.10.0", + "@typescript-eslint/utils": "8.10.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -4477,9 +4478,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.10.0.tgz", + "integrity": "sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4490,13 +4491,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.10.0.tgz", + "integrity": "sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/visitor-keys": "8.10.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4554,15 +4555,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", - "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.10.0.tgz", + "integrity": "sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1" + "@typescript-eslint/scope-manager": "8.10.0", + "@typescript-eslint/types": "8.10.0", + "@typescript-eslint/typescript-estree": "8.10.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4576,12 +4577,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.10.0.tgz", + "integrity": "sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.8.1", + "@typescript-eslint/types": "8.10.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -4625,9 +4626,9 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.2.tgz", - "integrity": "sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", "dev": true, "dependencies": { "@babel/core": "^7.25.2", @@ -4644,9 +4645,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.2.tgz", - "integrity": "sha512-b7kHrFrs2urS0cOk5N10lttI8UdJ/yP3nB4JYTREvR5o18cR99yPpK4gK8oQgI42BVv0ILWYUSYB7AXkAUDc0g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz", + "integrity": "sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -4666,8 +4667,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.2", - "vitest": "2.1.2" + "@vitest/browser": "2.1.3", + "vitest": "2.1.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -4676,13 +4677,13 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", - "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.3.tgz", + "integrity": "sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==", "dev": true, "dependencies": { - "@vitest/spy": "2.1.2", - "@vitest/utils": "2.1.2", + "@vitest/spy": "2.1.3", + "@vitest/utils": "2.1.3", "chai": "^5.1.1", "tinyrainbow": "^1.2.0" }, @@ -4691,12 +4692,12 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz", - "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.3.tgz", + "integrity": "sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==", "dev": true, "dependencies": { - "@vitest/spy": "^2.1.0-beta.1", + "@vitest/spy": "2.1.3", "estree-walker": "^3.0.3", "magic-string": "^0.30.11" }, @@ -4704,7 +4705,7 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.2", + "@vitest/spy": "2.1.3", "msw": "^2.3.5", "vite": "^5.0.0" }, @@ -4718,9 +4719,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz", - "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.3.tgz", + "integrity": "sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -4730,12 +4731,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz", - "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.3.tgz", + "integrity": "sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==", "dev": true, "dependencies": { - "@vitest/utils": "2.1.2", + "@vitest/utils": "2.1.3", "pathe": "^1.1.2" }, "funding": { @@ -4743,12 +4744,12 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz", - "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.3.tgz", + "integrity": "sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.2", + "@vitest/pretty-format": "2.1.3", "magic-string": "^0.30.11", "pathe": "^1.1.2" }, @@ -4757,9 +4758,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz", - "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.3.tgz", + "integrity": "sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==", "dev": true, "dependencies": { "tinyspy": "^3.0.0" @@ -4769,12 +4770,12 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz", - "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.3.tgz", + "integrity": "sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.1.2", + "@vitest/pretty-format": "2.1.3", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" }, @@ -7484,9 +7485,9 @@ } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.12.tgz", - "integrity": "sha512-9neVjoGv20FwYtCP6CB1dzR1vr57ZDNOXst21wd2xJ/cTlM2xLq0GWVlSNTdMn/4BtP6cHYBMCSp1wFBJ9jBsg==", + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz", + "integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==", "dev": true, "peerDependencies": { "eslint": ">=7" @@ -8107,15 +8108,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -9098,9 +9090,9 @@ } }, "node_modules/i18next": { - "version": "23.15.2", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.2.tgz", - "integrity": "sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==", + "version": "23.16.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.1.tgz", + "integrity": "sha512-H73h/H7BN7PI38Sq9XsOXzWFBH6mtyCYFiUMVtd9BxiYNDWPPIzKcBmDrqhjKbw3IXP5j6JoSW4ugJlaZuOvKw==", "funding": [ { "type": "individual", @@ -10273,13 +10265,10 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true }, "node_modules/lru-cache": { "version": "5.1.1", @@ -10571,9 +10560,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/msw": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.4.10.tgz", - "integrity": "sha512-bDQh9b25JK4IKMs5hnamwAkcNZ9RwA4mR/4YcgWkzwHOxj7UICbVJfmChJvY1UCAAMraPpvjHdxjoUDpc3F+Qw==", + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.4.11.tgz", + "integrity": "sha512-TVEw9NOPTc6ufOQLJ53234S9NBRxQbu7xFMxs+OCP43JQcNEIOKiZHxEm2nDzYIrwccoIhUxUf8wr99SukD76A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -10589,10 +10578,10 @@ "graphql": "^16.8.1", "headers-polyfill": "^4.0.2", "is-node-process": "^1.2.0", - "outvariant": "^1.4.2", + "outvariant": "^1.4.3", "path-to-regexp": "^6.3.0", "strict-event-emitter": "^0.5.1", - "type-fest": "^4.9.0", + "type-fest": "^4.26.1", "yargs": "^17.7.2" }, "bin": { @@ -10620,9 +10609,9 @@ "dev": true }, "node_modules/msw/node_modules/type-fest": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", - "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==", + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", "dev": true, "engines": { "node": ">=16" @@ -11732,15 +11721,26 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz", + "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" }, "node_modules/react-i18next": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", - "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.3.tgz", + "integrity": "sha512-BlO1P+oLKjjIxDBQ0GkAIMacgjfMbnvops+3Y5nZXF7UJ99v4KCWr0Na1azJXC8AMiNWp4kgUcFCJM7U9ZsUDg==", "dependencies": { "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" @@ -12547,9 +12547,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.79.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.79.5.tgz", - "integrity": "sha512-W1h5kp6bdhqFh2tk3DsI771MoEJjvrSY/2ihJRJS4pjIyfJCw0nTsxqhnrUzaLMOJjFchj8rOvraI/YUVjtx5g==", + "version": "1.80.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz", + "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==", "dev": true, "dependencies": { "@parcel/watcher": "^2.4.1", @@ -13300,9 +13300,9 @@ } }, "node_modules/terser": { - "version": "5.34.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", - "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -13456,9 +13456,9 @@ "dev": true }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", "dev": true }, "node_modules/tinypool": { @@ -13996,9 +13996,9 @@ } }, "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "version": "5.4.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.9.tgz", + "integrity": "sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==", "dev": true, "dependencies": { "esbuild": "^0.21.3", @@ -14055,9 +14055,9 @@ } }, "node_modules/vite-node": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz", - "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.3.tgz", + "integrity": "sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -14076,18 +14076,18 @@ } }, "node_modules/vitest": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz", - "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.3.tgz", + "integrity": "sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==", "dev": true, "dependencies": { - "@vitest/expect": "2.1.2", - "@vitest/mocker": "2.1.2", - "@vitest/pretty-format": "^2.1.2", - "@vitest/runner": "2.1.2", - "@vitest/snapshot": "2.1.2", - "@vitest/spy": "2.1.2", - "@vitest/utils": "2.1.2", + "@vitest/expect": "2.1.3", + "@vitest/mocker": "2.1.3", + "@vitest/pretty-format": "^2.1.3", + "@vitest/runner": "2.1.3", + "@vitest/snapshot": "2.1.3", + "@vitest/spy": "2.1.3", + "@vitest/utils": "2.1.3", "chai": "^5.1.1", "debug": "^4.3.6", "magic-string": "^0.30.11", @@ -14098,7 +14098,7 @@ "tinypool": "^1.0.0", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.2", + "vite-node": "2.1.3", "why-is-node-running": "^2.3.0" }, "bin": { @@ -14113,8 +14113,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.2", - "@vitest/ui": "2.1.2", + "@vitest/browser": "2.1.3", + "@vitest/ui": "2.1.3", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 0b36802..68ef9e1 100644 --- a/package.json +++ b/package.json @@ -31,20 +31,21 @@ "@fortawesome/fontawesome-svg-core": "6.6.0", "@fortawesome/free-solid-svg-icons": "6.6.0", "@fortawesome/react-fontawesome": "0.2.2", - "@ionic/react": "8.3.2", - "@ionic/react-router": "8.3.2", - "@tanstack/react-query": "5.59.9", - "@tanstack/react-query-devtools": "5.59.9", + "@ionic/react": "8.3.3", + "@ionic/react-router": "8.3.3", + "@tanstack/react-query": "5.59.15", + "@tanstack/react-query-devtools": "5.59.15", "axios": "1.7.7", "classnames": "2.5.1", "dayjs": "1.11.13", "formik": "2.4.6", - "i18next": "23.15.2", + "i18next": "23.16.1", "i18next-browser-languagedetector": "8.0.0", "lodash": "4.17.21", "react": "18.3.1", "react-dom": "18.3.1", - "react-i18next": "15.0.2", + "react-error-boundary": "4.1.2", + "react-i18next": "15.0.3", "react-router": "5.3.4", "react-router-dom": "5.3.4", "uuid": "10.0.0", @@ -53,31 +54,31 @@ "devDependencies": { "@capacitor/cli": "6.1.2", "@testing-library/dom": "10.4.0", - "@testing-library/jest-dom": "6.5.0", + "@testing-library/jest-dom": "6.6.2", "@testing-library/react": "16.0.1", "@testing-library/user-event": "14.5.2", - "@types/lodash": "4.17.10", + "@types/lodash": "4.17.12", "@types/react": "18.3.11", "@types/react-dom": "18.3.1", "@types/react-router": "5.1.20", "@types/react-router-dom": "5.3.3", "@types/uuid": "10.0.0", - "@typescript-eslint/eslint-plugin": "8.8.1", - "@typescript-eslint/parser": "8.8.1", + "@typescript-eslint/eslint-plugin": "8.10.0", + "@typescript-eslint/parser": "8.10.0", "@vitejs/plugin-legacy": "5.4.2", - "@vitejs/plugin-react": "4.3.2", - "@vitest/coverage-v8": "2.1.2", + "@vitejs/plugin-react": "4.3.3", + "@vitest/coverage-v8": "2.1.3", "cypress": "13.15.0", "eslint": "8.57.0", "eslint-plugin-react": "7.37.1", "eslint-plugin-react-hooks": "5.0.0", - "eslint-plugin-react-refresh": "0.4.12", + "eslint-plugin-react-refresh": "0.4.13", "jsdom": "25.0.1", - "msw": "2.4.10", - "sass": "1.79.5", - "terser": "5.34.1", + "msw": "2.4.11", + "sass": "1.80.3", + "terser": "5.36.0", "typescript": "5.5.4", - "vite": "5.4.8", - "vitest": "2.1.2" + "vite": "5.4.9", + "vitest": "2.1.3" } } diff --git a/src/App.tsx b/src/App.tsx index e460113..9e3c9df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,9 @@ import { IonApp, setupIonicReact } from '@ionic/react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ErrorBoundary } from 'react-error-boundary'; +import ErrorPage from 'common/components/Error/ErrorPage'; import ConfigContextProvider from './common/providers/ConfigProvider'; import { queryClient } from 'common/utils/query-client'; import AuthProvider from 'common/providers/AuthProvider'; @@ -11,7 +13,7 @@ import ScrollProvider from 'common/providers/ScrollProvider'; import Toasts from 'common/components/Toast/Toasts'; import AppRouter from 'common/components/Router/AppRouter'; -import './theme/main.scss'; +import './theme/main.css'; setupIonicReact(); @@ -22,21 +24,23 @@ setupIonicReact(); */ const App = (): JSX.Element => ( - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + ); diff --git a/src/assets/img/face_surprise_melting.png b/src/assets/img/face_surprise_melting.png new file mode 100644 index 0000000..48563b3 Binary files /dev/null and b/src/assets/img/face_surprise_melting.png differ diff --git a/src/common/components/Error/ErrorPage.scss b/src/common/components/Error/ErrorPage.scss new file mode 100644 index 0000000..d6b7d33 --- /dev/null +++ b/src/common/components/Error/ErrorPage.scss @@ -0,0 +1,23 @@ +.ls-error-page { + &__container { + max-width: 576px; + } + + &__content { + display: flex; + flex-direction: column; + align-items: center; + } + + &__title { + margin-bottom: 2rem; + } + + &__button-row { + margin-top: 4rem; + + ion-button { + min-width: 12rem; + } + } +} diff --git a/src/common/components/Error/ErrorPage.tsx b/src/common/components/Error/ErrorPage.tsx new file mode 100644 index 0000000..7e2e9fd --- /dev/null +++ b/src/common/components/Error/ErrorPage.tsx @@ -0,0 +1,92 @@ +import { IonButton, IonButtons, IonContent, IonFooter, IonPage, IonToolbar } from '@ionic/react'; +import { FallbackProps } from 'react-error-boundary'; +import { useTranslation } from 'react-i18next'; +import { ValidationError } from 'yup'; +import { AxiosError } from 'axios'; + +import image from 'assets/img/face_surprise_melting.png'; +import { PropsWithTestId } from '../types'; +import './ErrorPage.scss'; +import Header from '../Header/Header'; +import Container from '../Content/Container'; +import ButtonRow from '../Button/ButtonRow'; + +/** + * Properties for the `ErrorPage` component. + */ +interface ErrorPageProps extends FallbackProps, PropsWithTestId {} + +/** + * The `ErrorPage` displays the attributes of an `Error`. + * @param {ErrorPageProps} props - Component properties. + * @returns {JSX.Element} JSX + */ +const ErrorPage = ({ + error, + resetErrorBoundary, + testid = 'page-error', +}: ErrorPageProps): JSX.Element => { + const { t } = useTranslation(); + + let title; + let message; + if (error instanceof ValidationError) { + title = t('error-validation'); + message = error.errors.reduce((msg, error) => `${msg} ${error}`); + } else if (error instanceof AxiosError) { + title = error.status ?? error.code; + message = `${error.message}. ${error.config?.url}`; + } else { + title = error.name ?? t('error'); + message = error.message ?? error; + } + + return ( + +
+ + + +
+ {title} + +
+ {title} +
+ +
+ {message} +
+ + + resetErrorBoundary()} + data-testid={`${testid}-button`} + > + {t('label.try-again')} + + +
+
+
+ + + + resetErrorBoundary()} data-testid={`${testid}-footer-button`}> + {t('label.try-again')} + + + + + + ); +}; + +export default ErrorPage; diff --git a/src/common/components/Error/__tests__/ErrorPage.test.tsx b/src/common/components/Error/__tests__/ErrorPage.test.tsx new file mode 100644 index 0000000..b643c20 --- /dev/null +++ b/src/common/components/Error/__tests__/ErrorPage.test.tsx @@ -0,0 +1,145 @@ +import { describe, expect, it, vi } from 'vitest'; +import userEvent from '@testing-library/user-event'; +import { ValidationError } from 'yup'; +import { AxiosError } from 'axios'; + +import { render, screen } from 'test/test-utils'; + +import ErrorPage from '../ErrorPage'; + +describe('ErrorPage', () => { + it('should render successfully', async () => { + // ARRANGE + const error = new Error('error message'); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + }); + + it('should display ValidationError', async () => { + // ARRANGE + const ve1 = new ValidationError('Required.'); + const ve2 = new ValidationError('Max length is 100.'); + const error = new ValidationError([ve1, ve2]); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^Validation Error$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent( + /^Required. Max length is 100.$/i, + ); + }); + + it('should display AxiosError', async () => { + // ARRANGE + const config = { + url: 'http://www.example.org/', + }; + // @ts-expect-error Only need partial object for test + const error = new AxiosError('error message', AxiosError.ERR_BAD_REQUEST, config); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^ERR_BAD_REQUEST$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent( + /^error message. http:\/\/www.example.org\/$/i, + ); + }); + + it('should display AxiosError with status code', async () => { + // ARRANGE + const config = { + url: 'http://www.example.org/', + }; + // @ts-expect-error Only need partial object for test + const error = new AxiosError('error message', AxiosError.ERR_BAD_REQUEST, config, config, { + status: 404, + }); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^404$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent( + /^error message. http:\/\/www.example.org\/$/i, + ); + }); + + it('should display Error', async () => { + // ARRANGE + const error = new Error('error message'); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^Error$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent(/^error message$/i); + }); + + it('should display Error name', async () => { + // ARRANGE + const error = new Error('error message'); + error.name = 'SpecificError'; + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^SpecificError$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent(/^error message$/i); + }); + + it('should display plain error', async () => { + // ARRANGE + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error'); + + // ASSERT + expect(screen.getByTestId('page-error')).toBeDefined(); + expect(screen.getByTestId('page-error-title')).toHaveTextContent(/^Error$/i); + expect(screen.getByTestId('page-error-message')).toHaveTextContent(/^error message$/i); + }); + + it('should attempt to reset clicking page body button', async () => { + // ARRANGE + const user = userEvent.setup(); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error-button'); + + // ACT + await user.click(screen.getByTestId('page-error-button')); + + // ASSERT + expect(mockReset).toHaveBeenCalledTimes(1); + }); + + it('should attempt to reset clicking footer button', async () => { + // ARRANGE + const user = userEvent.setup(); + const mockReset = vi.fn(); + render(); + await screen.findByTestId('page-error-footer-button'); + + // ACT + await user.click(screen.getByTestId('page-error-footer-button')); + + // ASSERT + expect(mockReset).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/common/providers/ConfigProvider.tsx b/src/common/providers/ConfigProvider.tsx index 8860599..b52912d 100644 --- a/src/common/providers/ConfigProvider.tsx +++ b/src/common/providers/ConfigProvider.tsx @@ -1,5 +1,6 @@ import React, { PropsWithChildren, useEffect, useState } from 'react'; import { ObjectSchema, ValidationError, number, object, string } from 'yup'; +import { useTranslation } from 'react-i18next'; /** * The application configuration. The `value` provided by the `ConfigContext`. @@ -18,24 +19,6 @@ export interface Config { VITE_TOAST_AUTO_DISMISS_MILLIS: number; } -/** - * The configuration validation schema. - * @see {@link https://github.com/jquense/yup | Yup} - */ -const configSchema: ObjectSchema = object({ - VITE_BASE_URL_API: string().url().required(), - VITE_BUILD_DATE: string().default('1970-01-01'), - VITE_BUILD_TIME: string().default('00:00:00'), - VITE_BUILD_TS: string().default('1970-01-01T00:00:00+0000'), - VITE_BUILD_COMMIT_SHA: string().default('local'), - VITE_BUILD_ENV_CODE: string().default('local'), - VITE_BUILD_WORKFLOW_RUNNER: string().default('local'), - VITE_BUILD_WORKFLOW_NAME: string().default('local'), - VITE_BUILD_WORKFLOW_RUN_NUMBER: number().default(1), - VITE_BUILD_WORKFLOW_RUN_ATTEMPT: number().default(-1), - VITE_TOAST_AUTO_DISMISS_MILLIS: number().default(5000), -}); - /** * The `ConfigContext` instance. */ @@ -51,9 +34,30 @@ export const ConfigContext = React.createContext(undefined); * @returns {JSX.Element} JSX */ const ConfigContextProvider = ({ children }: PropsWithChildren): JSX.Element => { + const { t } = useTranslation(); const [isReady, setIsReady] = useState(false); const [config, setConfig] = useState(); + /** + * The configuration validation schema. + * @see {@link https://github.com/jquense/yup | Yup} + */ + const configSchema: ObjectSchema = object({ + VITE_BASE_URL_API: string() + .url() + .required(({ path }) => t('validation.required-path', { path })), + VITE_BUILD_DATE: string().default('1970-01-01'), + VITE_BUILD_TIME: string().default('00:00:00'), + VITE_BUILD_TS: string().default('1970-01-01T00:00:00+0000'), + VITE_BUILD_COMMIT_SHA: string().default('local'), + VITE_BUILD_ENV_CODE: string().default('local'), + VITE_BUILD_WORKFLOW_RUNNER: string().default('local'), + VITE_BUILD_WORKFLOW_NAME: string().default('local'), + VITE_BUILD_WORKFLOW_RUN_NUMBER: number().default(1), + VITE_BUILD_WORKFLOW_RUN_ATTEMPT: number().default(-1), + VITE_TOAST_AUTO_DISMISS_MILLIS: number().default(5000), + }); + useEffect(() => { try { const validatedConfig = configSchema.validateSync(import.meta.env, { @@ -63,10 +67,17 @@ const ConfigContextProvider = ({ children }: PropsWithChildren): JSX.Element => setConfig(validatedConfig); setIsReady(true); } catch (err) { - if (err instanceof ValidationError) throw new Error(`${err}::${err.errors}`); - if (err instanceof Error) throw new Error(`Configuration error: ${err.message}`); + if (err instanceof ValidationError) { + throw new Error( + `${t('error-configuration-validation')}. ${err.errors.reduce( + (msg, error) => `${msg} ${error}`, + )}`, + ); + } + if (err instanceof Error) throw new Error(`${t('error-configuration')}. ${err.message}`); throw err; } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/src/common/utils/i18n/resources/en/common.json b/src/common/utils/i18n/resources/en/common.json index 8d6efbf..ea4612f 100644 --- a/src/common/utils/i18n/resources/en/common.json +++ b/src/common/utils/i18n/resources/en/common.json @@ -2,15 +2,20 @@ "confirm-prompt": "Are you sure?", "created": "created", "deleted": "deleted", + "error": "Error", + "error-configuration": "Configuration error", + "error-configuration-validation": "Configuration validation error", "error-no-data": "No data", "error-generic": "Uh oh", + "error-validation": "Validation Error", "ionic-playground": "Ionic Playground", "label": { "cancel": "Cancel", "delete": "Delete", "dismiss": "Dismiss", "edit": "Edit", - "save": "Save" + "save": "Save", + "try-again": "Try Again" }, "navigation": { "account": "Account", @@ -25,6 +30,7 @@ "min": "Must be at least {{min}} characters. ", "oneOf": "Must be one of: {{values}} ", "required": "Required. ", + "required-path": "{{path}} is required. ", "url": "Must be a URL. " }, "no": "no", diff --git a/src/common/utils/i18n/resources/es/common.json b/src/common/utils/i18n/resources/es/common.json index d87a5a1..25c8e91 100644 --- a/src/common/utils/i18n/resources/es/common.json +++ b/src/common/utils/i18n/resources/es/common.json @@ -2,15 +2,20 @@ "confirm-prompt": "Estas seguro?", "created": "creado", "deleted": "eliminado", + "error": "Error", + "error-configuration": "Error de configuración", + "error-configuration-validation": "Error de validación de configuración", "error-no-data": "Sin datos", "error-generic": "Un problema", + "error-validation": "Error de validación", "ionic-playground": "Proyecto Ionic", "label": { "cancel": "Cancelar", "delete": "Borrar", "dismiss": "Despedir", "edit": "Editar", - "save": "Guardar" + "save": "Guardar", + "try-again": "Intentar otra vez" }, "navigation": { "account": "Cuenta", @@ -25,6 +30,7 @@ "min": "Debe tener al menos {{min}} caracteres. ", "oneOf": "Debe ser uno de: {{values}} ", "required": "Requerido. ", + "required-path": "{{path}} es obligatorio. ", "url": "Debe ser una URL. " }, "no": "no", diff --git a/src/common/utils/i18n/resources/fr/common.json b/src/common/utils/i18n/resources/fr/common.json index 5cf1d3d..321e583 100644 --- a/src/common/utils/i18n/resources/fr/common.json +++ b/src/common/utils/i18n/resources/fr/common.json @@ -2,15 +2,20 @@ "confirm-prompt": "Es-tu sûr??", "created": "créé", "deleted": "supprimé", + "error": "Erreur", + "error-configuration": "Erreur de configuration", + "error-configuration-validation": "Erreur de validation de configuration", "error-no-data": "Aucune donnée", "error-generic": "Un problème", + "error-validation": "Erreur de validation", "ionic-playground": "Projet Ionic", "label": { "cancel": "Annuler", "delete": "Supprimer", "dismiss": "Rejeter", "edit": "Modifier", - "save": "Sauvegarder" + "save": "Sauvegarder", + "try-again": "Essayer à nouveau" }, "navigation": { "account": "Compte", @@ -25,6 +30,7 @@ "min": "Doit contenir au moins {{min}} caractères. ", "oneOf": "Doit être l'un des: {{values}} ", "required": "Requis. ", + "required-path": "{{path}} est obligatoire. ", "url": "Doit être une URL. " }, "no": "non", diff --git a/src/pages/Account/__tests__/AccountPage.test.tsx b/src/pages/Account/__tests__/AccountPage.test.tsx index cea27e9..f8219e4 100644 --- a/src/pages/Account/__tests__/AccountPage.test.tsx +++ b/src/pages/Account/__tests__/AccountPage.test.tsx @@ -10,6 +10,7 @@ describe('AccountPage', () => { render(); await screen.findByTestId('page-account'); + // ASSERT expect(screen.getByTestId('page-account')).toBeDefined(); }); }); diff --git a/src/theme/fonts.scss b/src/theme/fonts.css similarity index 100% rename from src/theme/fonts.scss rename to src/theme/fonts.css diff --git a/src/theme/grid.scss b/src/theme/grid.css similarity index 99% rename from src/theme/grid.scss rename to src/theme/grid.css index 9032c36..7e6d239 100644 --- a/src/theme/grid.scss +++ b/src/theme/grid.css @@ -1,4 +1,4 @@ -// Flexbox and grid styles inspired by Tailwind +/* Flexbox and grid styles inspired by Tailwind */ :root { .order-1 { order: 1; diff --git a/src/theme/main.scss b/src/theme/main.css similarity index 79% rename from src/theme/main.scss rename to src/theme/main.css index eff6f59..4c8bb9f 100644 --- a/src/theme/main.scss +++ b/src/theme/main.css @@ -26,13 +26,13 @@ @import '@ionic/react/css/palettes/dark.system.css'; /* Custom app CSS */ -// normalize / reset -@import './normalize.scss'; -// variables / Ionic variable overrides -@import './variables.scss'; -// fonts -@import './fonts.scss'; -// typography -@import './typography.scss'; -// flexbox and grid -@import './grid.scss'; +/* normalize / reset */ +@import './normalize.css'; +/* variables / Ionic variable overrides */ +@import './variables.css'; +/* fonts */ +@import './fonts.css'; +/* typography */ +@import './typography.css'; +/* flexbox and grid */ +@import './grid.css'; diff --git a/src/theme/normalize.css b/src/theme/normalize.css new file mode 100644 index 0000000..148af00 --- /dev/null +++ b/src/theme/normalize.css @@ -0,0 +1,5 @@ +/* Normalization (reset) styles for the app */ +:root { + /* line height */ + line-height: 1.5; +} diff --git a/src/theme/normalize.scss b/src/theme/normalize.scss deleted file mode 100644 index de96044..0000000 --- a/src/theme/normalize.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Normalization (reset) styles for the app -:root { - // line height - line-height: 1.5; -} diff --git a/src/theme/typography.scss b/src/theme/typography.css similarity index 91% rename from src/theme/typography.scss rename to src/theme/typography.css index c35c0f7..d51a4eb 100644 --- a/src/theme/typography.scss +++ b/src/theme/typography.css @@ -1,6 +1,6 @@ -// Typography styles inspired by Tailwind +/* Typography styles inspired by Tailwind */ :root { - // font size + /* font size */ .text-xs { font-size: 0.75rem; line-height: 1rem; @@ -34,7 +34,7 @@ line-height: 2.5rem; } - // font weight + /* font weight */ .font-thin { font-weight: 100; } @@ -63,7 +63,7 @@ font-weight: 900; } - // word break + /* word break */ .break-normal { overflow-wrap: normal; word-break: normal; @@ -78,7 +78,7 @@ word-break: keep-all; } - // text transform + /* text transform */ .capitalize { text-transform: capitalize; } diff --git a/src/theme/variables.scss b/src/theme/variables.css similarity index 90% rename from src/theme/variables.scss rename to src/theme/variables.css index 3c90803..7e2c34a 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.css @@ -2,7 +2,7 @@ http://ionicframework.com/docs/theming/ */ :root { - // media breakpoints + /* media breakpoints */ --ls-breakpoint-xs: 0px; --ls-breakpoint-sm: 576px; --ls-breakpoint-md: 768px; diff --git a/vite.config.ts b/vite.config.ts index 0b4cdb1..1509780 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,6 +30,7 @@ export default defineConfig({ exclude: [ '**/__fixtures__/**', '**/__mocks__/**', + 'android/**', 'src/main.tsx', 'src/test', 'capacitor.config.ts',