From af47446470976406ae91fec62fb1ff763e830b63 Mon Sep 17 00:00:00 2001 From: Fathia Oyinloye Date: Wed, 29 Jul 2026 15:45:32 +0000 Subject: [PATCH 1/5] fix(store): restore Date objects when rehydrating persisted state --- package-lock.json | 320 ----------------------------- src/app/store/notificationStore.ts | 20 +- src/app/store/volunteerStore.ts | 8 +- 3 files changed, 23 insertions(+), 325 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59ccc925..b57832e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^3.10.0", "@monaco-editor/react": "^4.7.0", "@testing-library/dom": "^10.4.1", @@ -48,8 +47,6 @@ "react": "^18.3.1", "react-big-calendar": "1.19.4", "react-countdown": "^2.3.6", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", "react-hook-form": "^7.60.0", "react-hot-toast": "^2.6.0", @@ -2678,22 +2675,6 @@ "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, - "node_modules/@hello-pangea/dnd": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", - "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", - "dependencies": { - "@babel/runtime": "^7.26.7", - "css-box-model": "^1.2.1", - "raf-schd": "^4.0.3", - "react-redux": "^9.2.0", - "redux": "^5.0.1" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -4690,21 +4671,6 @@ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" }, - "node_modules/@react-dnd/asap": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" - }, - "node_modules/@react-dnd/invariant": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" - }, - "node_modules/@react-dnd/shallowequal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" - }, "node_modules/@reown/appkit": { "version": "1.7.17", "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.17.tgz", @@ -11390,14 +11356,6 @@ "node": ">=8" } }, - "node_modules/css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "dependencies": { - "tiny-invariant": "^1.0.6" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -11812,24 +11770,6 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, - "node_modules/dnd-core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", - "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", - "dependencies": { - "@react-dnd/asap": "^5.0.1", - "@react-dnd/invariant": "^4.0.1", - "redux": "^4.2.0" - } - }, - "node_modules/dnd-core/node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -18309,11 +18249,6 @@ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, - "node_modules/raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -18381,43 +18316,6 @@ "react-dom": ">= 15" } }, - "node_modules/react-dnd": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", - "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", - "dependencies": { - "@react-dnd/invariant": "^4.0.1", - "@react-dnd/shallowequal": "^4.0.1", - "dnd-core": "^16.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - }, - "peerDependencies": { - "@types/hoist-non-react-statics": ">= 3.3.1", - "@types/node": ">= 12", - "@types/react": ">= 16", - "react": ">= 16.14" - }, - "peerDependenciesMeta": { - "@types/hoist-non-react-statics": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-dnd-html5-backend": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", - "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", - "dependencies": { - "dnd-core": "^16.0.1" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -18537,28 +18435,6 @@ "react-dom": ">=16.3.0" } }, - "node_modules/react-redux": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -18798,11 +18674,6 @@ "node": ">=8" } }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -24794,18 +24665,6 @@ "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", "requires": {} }, - "@hello-pangea/dnd": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", - "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", - "requires": { - "@babel/runtime": "^7.26.7", - "css-box-model": "^1.2.1", - "raf-schd": "^4.0.3", - "react-redux": "^9.2.0", - "redux": "^5.0.1" - } - }, "@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -25936,21 +25795,6 @@ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" }, - "@react-dnd/asap": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" - }, - "@react-dnd/invariant": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" - }, - "@react-dnd/shallowequal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" - }, "@reown/appkit": { "version": "1.7.17", "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.17.tgz", @@ -30454,14 +30298,6 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, - "css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -30758,26 +30594,6 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, - "dnd-core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", - "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", - "requires": { - "@react-dnd/asap": "^5.0.1", - "@react-dnd/invariant": "^4.0.1", - "redux": "^4.2.0" - }, - "dependencies": { - "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } - } - } - }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -34151,7 +33967,6 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@eslint/eslintrc": "^3.3.5", - "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^3.10.0", "@monaco-editor/react": "^4.7.0", "@playwright/test": "^1.54.0", @@ -34214,8 +34029,6 @@ "react": "^18.3.1", "react-big-calendar": "1.19.4", "react-countdown": "^2.3.6", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.3.1", "react-hook-form": "^7.60.0", "react-hot-toast": "^2.6.0", @@ -35822,18 +35635,6 @@ "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", "requires": {} }, - "@hello-pangea/dnd": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", - "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", - "requires": { - "@babel/runtime": "^7.26.7", - "css-box-model": "^1.2.1", - "raf-schd": "^4.0.3", - "react-redux": "^9.2.0", - "redux": "^5.0.1" - } - }, "@hookform/resolvers": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", @@ -36964,21 +36765,6 @@ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" }, - "@react-dnd/asap": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" - }, - "@react-dnd/invariant": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" - }, - "@react-dnd/shallowequal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" - }, "@reown/appkit": { "version": "1.7.17", "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.17.tgz", @@ -41482,14 +41268,6 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, - "css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, "css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -41786,26 +41564,6 @@ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" }, - "dnd-core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", - "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", - "requires": { - "@react-dnd/asap": "^5.0.1", - "@react-dnd/invariant": "^4.0.1", - "redux": "^4.2.0" - }, - "dependencies": { - "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } - } - } - }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -46339,11 +46097,6 @@ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, - "raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -46399,26 +46152,6 @@ "prop-types": "^15.7.2" } }, - "react-dnd": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", - "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", - "requires": { - "@react-dnd/invariant": "^4.0.1", - "@react-dnd/shallowequal": "^4.0.1", - "dnd-core": "^16.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - } - }, - "react-dnd-html5-backend": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", - "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", - "requires": { - "dnd-core": "^16.0.1" - } - }, "react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -46489,15 +46222,6 @@ "warning": "^4.0.3" } }, - "react-redux": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "requires": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - } - }, "react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -46662,11 +46386,6 @@ "strip-indent": "^3.0.0" } }, - "redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -50768,11 +50487,6 @@ "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==" }, - "raf-schd": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -50828,26 +50542,6 @@ "prop-types": "^15.7.2" } }, - "react-dnd": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", - "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", - "requires": { - "@react-dnd/invariant": "^4.0.1", - "@react-dnd/shallowequal": "^4.0.1", - "dnd-core": "^16.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - } - }, - "react-dnd-html5-backend": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", - "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", - "requires": { - "dnd-core": "^16.0.1" - } - }, "react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -50918,15 +50612,6 @@ "warning": "^4.0.3" } }, - "react-redux": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", - "integrity": "sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==", - "requires": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - } - }, "react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -51091,11 +50776,6 @@ "strip-indent": "^3.0.0" } }, - "redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" - }, "reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", diff --git a/src/app/store/notificationStore.ts b/src/app/store/notificationStore.ts index 9604b32c..592646c2 100644 --- a/src/app/store/notificationStore.ts +++ b/src/app/store/notificationStore.ts @@ -2,11 +2,15 @@ import { create } from 'zustand'; import { AppNotification } from '@/lib/notifications/types'; import { NotificationService } from '@/lib/notifications/service'; -function load(key: string, fallback: T): T { +function load( + key: string, + fallback: T, + reviver?: (key: string, value: unknown) => unknown, +): T { if (typeof window === 'undefined') return fallback; try { const raw = localStorage.getItem(key); - return raw ? (JSON.parse(raw) as T) : fallback; + return raw ? (JSON.parse(raw, reviver) as T) : fallback; } catch { return fallback; } @@ -19,6 +23,16 @@ function save(key: string, value: T) { } catch {} } +/** Revives ISO date strings found in `timestamp` fields back to Date objects. */ +function dateReviver(key: string, value: unknown): unknown { + if (key === 'timestamp' && typeof value === 'string') { + const date = new Date(value); + // Only return Date if the string was a valid ISO date + if (!isNaN(date.getTime())) return date; + } + return value; +} + const STORAGE_KEY = 'notifications_v1'; interface NotificationState { @@ -33,7 +47,7 @@ interface NotificationState { } export const useNotificationStore = create((set, get) => ({ - notifications: load(STORAGE_KEY, []), + notifications: load(STORAGE_KEY, [], dateReviver), addNotification: (n) => { const created = NotificationService.createNotification({ message: n.message, diff --git a/src/app/store/volunteerStore.ts b/src/app/store/volunteerStore.ts index 76305f92..e73fd83a 100644 --- a/src/app/store/volunteerStore.ts +++ b/src/app/store/volunteerStore.ts @@ -3,11 +3,15 @@ import type { Volunteer, VolunteerSMSPreferences } from '@/types/volunteer'; const STORAGE_KEY = 'volunteers_v1'; -function load(key: string, fallback: T): T { +function load( + key: string, + fallback: T, + reviver?: (key: string, value: unknown) => unknown, +): T { if (typeof window === 'undefined') return fallback; try { const raw = localStorage.getItem(key); - return raw ? (JSON.parse(raw) as T) : fallback; + return raw ? (JSON.parse(raw, reviver) as T) : fallback; } catch { return fallback; } From 8cf6704a461fc32773eb5529a252726de9cf6c9e Mon Sep 17 00:00:00 2001 From: Fathia Oyinloye Date: Wed, 29 Jul 2026 16:28:30 +0000 Subject: [PATCH 2/5] fix(sms): resolve concurrent enqueue timeout and stale delivery log test failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Attach resolve callback to each InternalQueueJob so concurrent enqueues beyond maxConcurrent don't hang indefinitely. - Rename process() to processQueue() — drains jobs without needing an external resolve parameter. - Add SMSQueue.clearAllDeliveryLogs() static method for test isolation. - Fix metadata test to assert most recent log entry. --- src/__tests__/sms/queue.test.ts | 7 ++++--- src/lib/sms/queue.ts | 37 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/__tests__/sms/queue.test.ts b/src/__tests__/sms/queue.test.ts index 9bde8291..3dd92659 100644 --- a/src/__tests__/sms/queue.test.ts +++ b/src/__tests__/sms/queue.test.ts @@ -13,6 +13,7 @@ describe('SMSQueue', () => { let provider: TwilioProvider; beforeEach(() => { + SMSQueue.clearAllDeliveryLogs(); provider = new TwilioProvider(); queue = new SMSQueue(provider, { maxRetries: 3, @@ -251,9 +252,9 @@ describe('SMSQueue', () => { const logs = queue.getDeliveryLogs(); - if (logs.length > 0) { - expect(logs[0].metadata).toBeDefined(); - } + expect(logs.length).toBeGreaterThan(0); + // Logs are returned newest-last; check the most recent entry + expect(logs[logs.length - 1].metadata).toBeDefined(); }); }); }); diff --git a/src/lib/sms/queue.ts b/src/lib/sms/queue.ts index ef220558..fd75a814 100644 --- a/src/lib/sms/queue.ts +++ b/src/lib/sms/queue.ts @@ -29,10 +29,14 @@ function createJobId(): string { // In-memory delivery log store for aggregation const deliveryLogs: Map = new Map(); +interface InternalQueueJob extends QueueJob { + resolve: (result: SMSSendResult) => void; +} + export class SMSQueue { private readonly provider: SMSProvider; private readonly options: QueueOptions; - private readonly queue: QueueJob[] = []; + private readonly queue: InternalQueueJob[] = []; private processing = 0; private requestId = ''; @@ -53,16 +57,22 @@ export class SMSQueue { }); } + /** Clear all shared delivery logs (useful for testing). */ + static clearAllDeliveryLogs(): void { + deliveryLogs.clear(); + } + enqueue(message: SMSMessage): Promise { return new Promise((resolve) => { const jobId = createJobId(); this.requestId = `${jobId}_parent`; - const job: QueueJob = { + const job: InternalQueueJob = { id: jobId, message, attempts: 0, createdAt: Date.now(), + resolve, }; logger.info('SMS message enqueued', { @@ -80,11 +90,12 @@ export class SMSQueue { provider: this.provider.type, }); - this.process(resolve); + this.processQueue(); }); } - private process(resolve: (result: SMSSendResult) => void): void { + /** Drain the queue up to maxConcurrent limit. Each job carries its own resolve. */ + private processQueue(): void { while (this.processing < this.options.maxConcurrent && this.queue.length > 0) { const nextJob = this.queue.shift(); if (!nextJob) { @@ -92,16 +103,14 @@ export class SMSQueue { } this.processing += 1; - void this.runJob(nextJob) - .then((result) => resolve(result)) - .finally(() => { - this.processing -= 1; - this.process(resolve); - }); + void this.runJob(nextJob).finally(() => { + this.processing -= 1; + this.processQueue(); + }); } } - private async runJob(job: QueueJob): Promise { + private async runJob(job: InternalQueueJob): Promise { let result: SMSSendResult = { success: false, provider: this.provider.type, @@ -154,7 +163,8 @@ export class SMSQueue { provider: this.provider.type, }); - return result; + job.resolve(result); + return; } if (job.attempts < this.options.maxRetries) { @@ -195,10 +205,11 @@ export class SMSQueue { provider: this.provider.type, }); - return { + const finalResult: SMSSendResult = { ...result, error: `Queue failed after ${job.attempts} attempts: ${result.error ?? 'Unknown error'}`, }; + job.resolve(finalResult); } private logDeliveryAttempt( From fd532bb660e755c14b6ec45cd433d40f5686f5ba Mon Sep 17 00:00:00 2001 From: fathiaoyinloye Date: Sun, 2 Aug 2026 10:16:40 +0100 Subject: [PATCH 3/5] fixing workflow --- src/app/api/referral/__tests__/validate.test.ts | 4 ++-- src/app/store/notificationStore.ts | 6 +----- src/app/store/volunteerStore.ts | 6 +----- .../social/__tests__/commentSection.additional.test.tsx | 2 +- src/services/tipService.ts | 7 +++++-- src/utils/__tests__/intlCache.test.ts | 8 ++++++-- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/app/api/referral/__tests__/validate.test.ts b/src/app/api/referral/__tests__/validate.test.ts index 1f39b3fa..c72d6d6b 100644 --- a/src/app/api/referral/__tests__/validate.test.ts +++ b/src/app/api/referral/__tests__/validate.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { POST } from '../validate/route'; +import { GET } from '../validate/route'; describe('Referral Validation API', () => { describe('GET /api/referral/validate', () => { @@ -13,7 +13,7 @@ describe('Referral Validation API', () => { } as any; // The route handler exists and can be imported - expect(POST).toBeDefined(); + expect(GET).toBeDefined(); }); it('should handle missing code parameter', async () => { diff --git a/src/app/store/notificationStore.ts b/src/app/store/notificationStore.ts index 592646c2..76a7a2b8 100644 --- a/src/app/store/notificationStore.ts +++ b/src/app/store/notificationStore.ts @@ -2,11 +2,7 @@ import { create } from 'zustand'; import { AppNotification } from '@/lib/notifications/types'; import { NotificationService } from '@/lib/notifications/service'; -function load( - key: string, - fallback: T, - reviver?: (key: string, value: unknown) => unknown, -): T { +function load(key: string, fallback: T, reviver?: (key: string, value: unknown) => unknown): T { if (typeof window === 'undefined') return fallback; try { const raw = localStorage.getItem(key); diff --git a/src/app/store/volunteerStore.ts b/src/app/store/volunteerStore.ts index e73fd83a..c050fe4d 100644 --- a/src/app/store/volunteerStore.ts +++ b/src/app/store/volunteerStore.ts @@ -3,11 +3,7 @@ import type { Volunteer, VolunteerSMSPreferences } from '@/types/volunteer'; const STORAGE_KEY = 'volunteers_v1'; -function load( - key: string, - fallback: T, - reviver?: (key: string, value: unknown) => unknown, -): T { +function load(key: string, fallback: T, reviver?: (key: string, value: unknown) => unknown): T { if (typeof window === 'undefined') return fallback; try { const raw = localStorage.getItem(key); diff --git a/src/components/social/__tests__/commentSection.additional.test.tsx b/src/components/social/__tests__/commentSection.additional.test.tsx index f87ec8b1..921858d6 100644 --- a/src/components/social/__tests__/commentSection.additional.test.tsx +++ b/src/components/social/__tests__/commentSection.additional.test.tsx @@ -50,7 +50,7 @@ describe('SocialInteractions – Additional edge cases', () => { render(); await waitFor(() => expect(screen.getByLabelText('Copy link')).toBeInTheDocument()); - await userEvent.setup().click(screen.getByLabelLabel('Copy link')); + await userEvent.setup().click(screen.getByLabelText('Copy link')); expect(writeText).toHaveBeenCalledWith(customUrl); await waitFor(() => expect(screen.getByText('Copied!')).toBeInTheDocument()); }); diff --git a/src/services/tipService.ts b/src/services/tipService.ts index 6afbea1a..eb9f5627 100644 --- a/src/services/tipService.ts +++ b/src/services/tipService.ts @@ -25,8 +25,11 @@ export async function sendTip(payload: TipPayload): Promise { }); if (!response.ok) { - const message = - typeof responseBody.error === 'string' ? responseBody.error : 'Unable to send tip.'; + const responseBody = (await response.json().catch(() => null)) as { + error?: string; + message?: string; + } | null; + const message = responseBody?.error ?? responseBody?.message ?? 'Unable to send tip.'; throw new Error(message); } diff --git a/src/utils/__tests__/intlCache.test.ts b/src/utils/__tests__/intlCache.test.ts index 2b0005b1..40fc9542 100644 --- a/src/utils/__tests__/intlCache.test.ts +++ b/src/utils/__tests__/intlCache.test.ts @@ -145,16 +145,20 @@ describe('intlCache performance', () => { it('cache hit is significantly faster than cache miss (construction)', () => { const iterations = 10000; const locale = 'en-US'; + const options = { maximumFractionDigits: 2 }; + // Uncached construction: create a brand-new formatter every iteration const startMiss = performance.now(); for (let i = 0; i < iterations; i++) { - getNumberFormat(locale, { maximumFractionDigits: 2 }); + new Intl.NumberFormat(locale, options); } const timeMiss = performance.now() - startMiss; + // Cache hit: look up the same cached formatter every iteration + getNumberFormat(locale, options); const startHit = performance.now(); for (let i = 0; i < iterations; i++) { - getNumberFormat(locale, { maximumFractionDigits: 2 }); + getNumberFormat(locale, options); } const timeHit = performance.now() - startHit; From 42985da94f6261cc37e969b9fc3b6f055c2a24fd Mon Sep 17 00:00:00 2001 From: fathiaoyinloye Date: Sun, 2 Aug 2026 11:00:38 +0100 Subject: [PATCH 4/5] fix(test): correct getByLabelText typo in commentSection additional test --- .../social/__tests__/commentSection.additional.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/social/__tests__/commentSection.additional.test.tsx b/src/components/social/__tests__/commentSection.additional.test.tsx index 921858d6..bce9dbe8 100644 --- a/src/components/social/__tests__/commentSection.additional.test.tsx +++ b/src/components/social/__tests__/commentSection.additional.test.tsx @@ -51,7 +51,7 @@ describe('SocialInteractions – Additional edge cases', () => { render(); await waitFor(() => expect(screen.getByLabelText('Copy link')).toBeInTheDocument()); await userEvent.setup().click(screen.getByLabelText('Copy link')); - expect(writeText).toHaveBeenCalledWith(customUrl); + await waitFor(() => expect(writeText).toHaveBeenCalledWith(customUrl)); await waitFor(() => expect(screen.getByText('Copied!')).toBeInTheDocument()); }); }); From c91d364f396588f2574198baf1ab7ef81c2f5675 Mon Sep 17 00:00:00 2001 From: fathiaoyinloye Date: Sun, 2 Aug 2026 11:23:25 +0100 Subject: [PATCH 5/5] fix(sms): restore clean queue implementation after merge conflict --- src/lib/sms/queue.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/lib/sms/queue.ts b/src/lib/sms/queue.ts index f074eef8..fd75a814 100644 --- a/src/lib/sms/queue.ts +++ b/src/lib/sms/queue.ts @@ -37,8 +37,6 @@ export class SMSQueue { private readonly provider: SMSProvider; private readonly options: QueueOptions; private readonly queue: InternalQueueJob[] = []; - private readonly queue: QueueJob[] = []; - private readonly resolveQueue: Array<(result: SMSSendResult) => void> = []; private processing = 0; private requestId = ''; @@ -88,7 +86,6 @@ export class SMSQueue { }); this.queue.push(job); - this.resolveQueue.push(resolve); createCounterMetric('sms.enqueued', 1, { provider: this.provider.type, }); @@ -100,30 +97,16 @@ export class SMSQueue { /** Drain the queue up to maxConcurrent limit. Each job carries its own resolve. */ private processQueue(): void { while (this.processing < this.options.maxConcurrent && this.queue.length > 0) { - this.process(); - }); - } - - private process(): void { - while (this.processing < this.options.maxConcurrent && this.queue.length > 0 && this.resolveQueue.length > 0) { const nextJob = this.queue.shift(); if (!nextJob) { return; } - const resolve = this.resolveQueue.shift(); - this.processing += 1; void this.runJob(nextJob).finally(() => { this.processing -= 1; this.processQueue(); }); - void this.runJob(nextJob) - .then((result) => resolve?.(result)) - .finally(() => { - this.processing -= 1; - this.process(); - }); } }