diff --git a/package.json b/package.json index c6cf7135a2..a5c4a61d01 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@popperjs/core": "2.11.8", "@reduxjs/toolkit": "^1.9.6", "@sentry/react": "^7.107.0", - "axios": "^1.13.1", + "axios": "^1.16.0", "buffer": "^6.0.3", "chart.js": "^4.4.0", "chartjs-adapter-date-fns": "^3.0.0", @@ -71,7 +71,7 @@ "react-image-crop": "^10.1.8", "react-player": "^2.16.0", "react-redux": "^8.1.3", - "react-router-dom": "^6.30.3", + "react-router-dom": "^6.30.4", "react-secure-storage": "^1.3.2", "react-transition-group": "^4.4.5", "react-virtualized-auto-sizer": "^1.0.26", @@ -111,7 +111,8 @@ "crypto-js": "^4.2.0", "@babel/traverse": "^7.23.2", "nth-check": "2.1.1", - "ws": "8.17.1" + "ws": "8.17.1", + "lodash-es": ">=4.18.0" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.0", @@ -173,7 +174,7 @@ "vite-plugin-node-polyfills": "^0.24.0", "vite-plugin-svgr": "^4.3.0", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^3.0.8" + "vitest": "^3.2.6" }, "resolutions": { "@testing-library/dom": "9.3.3", @@ -189,6 +190,7 @@ "follow-redirects": ">=1.16.0", "js-yaml": ">=4.1.1", "lodash": ">=4.18.0", + "lodash-es": ">=4.18.0", "picomatch": ">=4.0.4", "postcss": ">=8.5.10", "qs": ">=6.15.2", diff --git a/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.test.tsx b/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.test.tsx index a82b197767..7d281ac13d 100644 --- a/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.test.tsx +++ b/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.test.tsx @@ -74,8 +74,7 @@ vi.mock('modules/Builder/hooks', () => ({ useCustomFormContext: () => ({ trigger: () => true, formState: { - dirtyFields: [], - isDirty: true, + dirtyFields: { about: true }, }, getValues: () => mockedAppletData, }), diff --git a/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.ts b/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.ts index d99fe61260..cd7aaec5fe 100644 --- a/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.ts +++ b/src/modules/Builder/features/SaveAndPublish/SaveAndPublish.hooks.ts @@ -292,7 +292,7 @@ export const useSaveAndPublishSetup = (): SaveAndPublishSetup => { const { t } = useTranslation('app'); const { trigger, - formState: { dirtyFields, isDirty }, + formState: { dirtyFields }, } = useCustomFormContext(); const { pathname } = useLocation(); const getAppletData = useAppletDataFromForm(); @@ -315,13 +315,30 @@ export const useSaveAndPublishSetup = (): SaveAndPublishSetup => { const hasAccessDeniedError = Array.isArray(responseError) && responseError.some((error) => error.type === ErrorResponseType.AccessDenied); + // Use `dirtyFields` (fields the user actually edited) rather than RHF's `isDirty`. + // `isDirty` is a structural deep-compare of the form values against the form defaults, + // so empty arrays that field arrays (e.g. `responseValues.rows`) inject into the form + // values on mount — but never into the defaults — make `isDirty` permanently `true` + // even with no real changes. + // + // A shallow `Object.keys(dirtyFields).length` check is also insufficient: after a + // useFieldArray append+remove round-trip, RHF leaves stale array-shaped entries in + // `dirtyFields` (e.g. `{ activities: [{ name: false, … }] }`) even though every leaf + // is `false`. Recursively scanning for any `true` leaf is the correct signal. + const hasTrueDirtyField = (val: unknown): boolean => { + if (val === true) return true; + if (!val || typeof val !== 'object') return false; + + return Object.values(val).some(hasTrueDirtyField); + }; + const hasFormChanges = hasTrueDirtyField(dirtyFields); const { cancelNavigation: onCancelNavigation, confirmNavigation, promptVisible, setPromptVisible, isLogoutInProgress, - } = usePrompt(isDirty && !hasAccessDeniedError); + } = usePrompt(hasFormChanges && !hasAccessDeniedError); const shouldNavigateRef = useRef(false); const appletUniqueNameRef = useRef(null); const { ownerId } = workspaces.useData() || {}; @@ -446,7 +463,7 @@ export const useSaveAndPublishSetup = (): SaveAndPublishSetup => { return; } - if (!isDirty) { + if (!hasFormChanges) { dispatch(banners.actions.addBanner({ key: 'AppletWithoutChangesBanner' })); return; diff --git a/yarn.lock b/yarn.lock index 43bed03aa2..139d65cc8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1800,10 +1800,10 @@ redux-thunk "^2.4.2" reselect "^4.1.8" -"@remix-run/router@1.23.2": - version "1.23.2" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.2.tgz#156c4b481c0bee22a19f7924728a67120de06971" - integrity sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w== +"@remix-run/router@1.23.3": + version "1.23.3" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.3.tgz#957c098d4393d301a8aa7dccf3ef28ea5430e36a" + integrity sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q== "@restart/hooks@^0.4.7": version "0.4.16" @@ -2895,64 +2895,64 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.17.0" -"@vitest/expect@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.4.tgz#8362124cd811a5ee11c5768207b9df53d34f2433" - integrity sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig== +"@vitest/expect@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-3.2.6.tgz#dc3a617acc1f29c132ed6be15456adb75c94a7a4" + integrity sha512-1+7q9BtaKzEmO+fmNT3kYvoNn5Y71XWAx2Q5HRim4tTVRQVRv4uJFAQ5FbK0OPUeNP/WmVCpxYxoJdvuHVjzBQ== dependencies: "@types/chai" "^5.2.2" - "@vitest/spy" "3.2.4" - "@vitest/utils" "3.2.4" + "@vitest/spy" "3.2.6" + "@vitest/utils" "3.2.6" chai "^5.2.0" tinyrainbow "^2.0.0" -"@vitest/mocker@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.2.4.tgz#4471c4efbd62db0d4fa203e65cc6b058a85cabd3" - integrity sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ== +"@vitest/mocker@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-3.2.6.tgz#fd0f2bc2a86d82b6013b3456ad662d04a3551434" + integrity sha512-EZOrpDbkKotFAP7wPAQV1UIyoGOk4oX7ynWhBhLB7v+meMHbQhU16oPpIYGTTe4oFlhpryGpgpcZP/sin3hYuw== dependencies: - "@vitest/spy" "3.2.4" + "@vitest/spy" "3.2.6" estree-walker "^3.0.3" magic-string "^0.30.17" -"@vitest/pretty-format@3.2.4", "@vitest/pretty-format@^3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz#3c102f79e82b204a26c7a5921bf47d534919d3b4" - integrity sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA== +"@vitest/pretty-format@3.2.6", "@vitest/pretty-format@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-3.2.6.tgz#5d1272700abe317f24b88009337b2b5cdaa68bf6" + integrity sha512-lb7XXXzmm2h2ASzFnRvQpDo6onT1NmMJA3tkGTWiBFtRJ9lxGY3d3mm/Apt36gej2bkkOVLL/yTOtufDaFa/jA== dependencies: tinyrainbow "^2.0.0" -"@vitest/runner@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.4.tgz#5ce0274f24a971f6500f6fc166d53d8382430766" - integrity sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ== +"@vitest/runner@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-3.2.6.tgz#6d9fb047b1430431987782e779aa889819a2e964" + integrity sha512-HYcoSj1w5tcgUnzoF0HcyaAQjpA1gj9ftUJ7iSJSuipc02jW9gKkigwZbjFldAfYHA1fa8UZVRftdMY5msWM9Q== dependencies: - "@vitest/utils" "3.2.4" + "@vitest/utils" "3.2.6" pathe "^2.0.3" strip-literal "^3.0.0" -"@vitest/snapshot@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.2.4.tgz#40a8bc0346ac0aee923c0eefc2dc005d90bc987c" - integrity sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ== +"@vitest/snapshot@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-3.2.6.tgz#3a9cb56389289028a511e97bbfd41d914004f3f5" + integrity sha512-H+ZjNTWGpObenh0YnlBctAPnJSI20P81PL8BPzWpx54YXLLTm8hEsWawtcYLMrwvpK48hGxLLbCS+1KRXhsKhw== dependencies: - "@vitest/pretty-format" "3.2.4" + "@vitest/pretty-format" "3.2.6" magic-string "^0.30.17" pathe "^2.0.3" -"@vitest/spy@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.4.tgz#cc18f26f40f3f028da6620046881f4e4518c2599" - integrity sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw== +"@vitest/spy@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-3.2.6.tgz#c255ab84df924b28d6fbec60a37a7f693db4ea41" + integrity sha512-oq6BbH68WzcWmwtBrU9nqLeaXTR4XwJF7FSLkKEZo4i6eoXcrxjcwSuTvWBIRUTC6VC72nXYunzqgZA+IKdtxg== dependencies: tinyspy "^4.0.3" -"@vitest/utils@3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.4.tgz#c0813bc42d99527fb8c5b138c7a88516bca46fea" - integrity sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA== +"@vitest/utils@3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-3.2.6.tgz#6fad3368e48b7a1d058827eb9b4bbc650a3f9402" + integrity sha512-lI23nIs4bnT3T8NIoh+vFaz5s2/DdP0Jgt2jxwgWljvwn82cLJtyi/If+fjFyoLMGIOz0U/fKvWE0d4jsNQEfg== dependencies: - "@vitest/pretty-format" "3.2.4" + "@vitest/pretty-format" "3.2.6" loupe "^3.1.4" tinyrainbow "^2.0.0" @@ -3203,13 +3203,14 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axios@^1.13.1: - version "1.15.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.2.tgz#eb8fb6d30349abace6ade5b4cb4d9e8a0dc23e5b" - integrity sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A== +axios@^1.16.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.17.0.tgz#ae5a1164a4f719942cd73c67e6a3f62d3ccb8f2b" + integrity sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw== dependencies: - follow-redirects "^1.15.11" + follow-redirects "^1.16.0" form-data "^4.0.5" + https-proxy-agent "^5.0.1" proxy-from-env "^2.1.0" babel-jest@30.1.2: @@ -3319,17 +3320,17 @@ bn.js@^5.2.1, bn.js@^5.2.2: integrity sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w== brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.15" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.15.tgz#a6d90d54067236e5f42570a3b7378d594d9b7738" + integrity sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^5.0.2: - version "5.0.3" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.3.tgz#6a9c6c268f85b53959ec527aeafe0f7300258eef" - integrity sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA== + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== dependencies: balanced-match "^4.0.2" @@ -4769,7 +4770,7 @@ flatted@>=3.4.2, flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== -follow-redirects@>=1.16.0, follow-redirects@^1.15.11: +follow-redirects@>=1.16.0, follow-redirects@^1.16.0: version "1.16.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== @@ -5105,7 +5106,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== -https-proxy-agent@^5.0.0: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -6371,10 +6372,10 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash-es@^4.17.15, lodash-es@^4.17.21: - version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0" - integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg== +lodash-es@>=4.18.0, lodash-es@^4.17.15, lodash-es@^4.17.21: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d" + integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== lodash.camelcase@^4.3.0: version "4.3.0" @@ -7559,20 +7560,20 @@ react-refresh@^0.17.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== -react-router-dom@^6.30.3: - version "6.30.3" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.3.tgz#42ae6dc4c7158bfb0b935f162b9621b29dddf740" - integrity sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag== +react-router-dom@^6.30.4: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.4.tgz#f7167bf3da6c7d9132130ea985dd06def25e84d5" + integrity sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q== dependencies: - "@remix-run/router" "1.23.2" - react-router "6.30.3" + "@remix-run/router" "1.23.3" + react-router "6.30.4" -react-router@6.30.3: - version "6.30.3" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.3.tgz#994b3ccdbe0e81fe84d4f998100f62584dfbf1cf" - integrity sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw== +react-router@6.30.4: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.4.tgz#638f35176527bd243d96d81d35d33b757bad46c2" + integrity sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA== dependencies: - "@remix-run/router" "1.23.2" + "@remix-run/router" "1.23.3" react-secure-storage@^1.3.2: version "1.3.2" @@ -8788,19 +8789,19 @@ vite@^7.3.2: optionalDependencies: fsevents "~2.3.3" -vitest@^3.0.8: - version "3.2.4" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.4.tgz#0637b903ad79d1539a25bc34c0ed54b5c67702ea" - integrity sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A== +vitest@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-3.2.6.tgz#de42ebfb58e16faba4e5a314fe35e146e03cb340" + integrity sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw== dependencies: "@types/chai" "^5.2.2" - "@vitest/expect" "3.2.4" - "@vitest/mocker" "3.2.4" - "@vitest/pretty-format" "^3.2.4" - "@vitest/runner" "3.2.4" - "@vitest/snapshot" "3.2.4" - "@vitest/spy" "3.2.4" - "@vitest/utils" "3.2.4" + "@vitest/expect" "3.2.6" + "@vitest/mocker" "3.2.6" + "@vitest/pretty-format" "^3.2.6" + "@vitest/runner" "3.2.6" + "@vitest/snapshot" "3.2.6" + "@vitest/spy" "3.2.6" + "@vitest/utils" "3.2.6" chai "^5.2.0" debug "^4.4.1" expect-type "^1.2.1"