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}
+
+
+
+ {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',