Skip to content

fix: adjust popup pattern, add isSaving on popups, return promises#994

Open
tomrndom wants to merge 3 commits into
masterfrom
fix/popup-pattern-deviations
Open

fix: adjust popup pattern, add isSaving on popups, return promises#994
tomrndom wants to merge 3 commits into
masterfrom
fix/popup-pattern-deviations

Conversation

@tomrndom

@tomrndom tomrndom commented Jun 19, 2026

Copy link
Copy Markdown

ref: https://app.clickup.com/t/9014802374/86bag2zk7

…emove open prop, adjust tests

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Prevented duplicate submissions by disabling save/close interactions during in-progress saves across multiple dialogs.
    • Updated dialog closing behavior so some dialogs now only dismiss after a successful save, while others remain open until the relevant list refreshes.
    • Improved inventory-save error handling so failures are no longer silently swallowed.
  • Refactor

    • Standardized save flows and refreshed lists with the correct paging/sort/filter settings; pagination now resets when view options change.
  • Tests

    • Updated dialog mock behaviors to match the new async save/close flow.

…emove open prop, adjust tests

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bbbcae84-a4d2-4aab-ac88-1e96ed0f1bfc

📥 Commits

Reviewing files that changed from the base of the PR and between 19a9c72 and 1344f0c.

📒 Files selected for processing (1)
  • src/actions/inventory-item-actions.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/actions/inventory-item-actions.js

📝 Walkthrough

Walkthrough

Across six popup/dialog components, isSaving state management is moved from parent list pages into the dialogs themselves. Each dialog now guards against duplicate submissions, disables close/escape UI while saving, self-closes after the save promise resolves, and drops the externally-controlled open prop. Parent list pages remove explicit post-save setOpen(false) calls and instead trigger list refreshes in .then() handlers. Action functions are updated to properly return promises, with savePageTemplate delegating list refresh responsibility to parent pages.

Changes

Dialog Save-State Internalization & Close-Responsibility Shift

Layer / File(s) Summary
Dialog components: internalize isSaving and self-close on success
src/pages/sponsorship-types/components/sponsorship-dialog.js, src/pages/sponsors-global/page-templates/page-template-popup/index.js, src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js, src/pages/sponsors/popup/add-sponsor-popup.js, src/pages/sponsors/popup/edit-tier-popup.js, src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js, src/pages/sponsors-global/form-templates/form-template-popup.js
Each dialog adds a local isSaving state with re-entry guard, sets/clears isSaving around the async save call, calls onClose on success in .then(), and disables Escape-key closing plus close icon and submit button while saving. SponsorshipDialog drops its external isSaving prop. FormTemplatePopup switches from Promise.resolve + closePopup to direct onSave().then(onClose). AddSponsorDialog and EditTierDialog also drop their open prop from the component API and PropTypes.
Parent pages: remove open prop, switch to conditional rendering
src/pages/sponsors/sponsor-list-page.js, src/pages/sponsors/summit-sponsorship-list-page.js, src/pages/sponsorship-types/sponsorship-list-page.js
SponsorListPage removes open={showAddSponsorModal} from AddSponsorDialog and relies on conditional mounting. SummitSponsorshipListPage switches EditTierPopup to conditional rendering without an open prop. SponsorshipListPage removes its local isSaving state, its isSaving prop to SponsorshipDialog, and simplifies both handleClose and handleSave.
Parent save handlers: remove dialog-close calls, add list refreshes
src/pages/sponsors-global/form-templates/form-template-item-list-page.js, src/pages/sponsors-global/form-templates/form-template-list-page.js, src/pages/sponsors-global/inventory/inventory-list-page.js, src/pages/sponsors-global/page-templates/page-template-list-page.js, src/pages/sponsors/show-pages-list-page/index.js, src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js, src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js, src/pages/tags/tag-list-page.js
Explicit setOpen(false) / setOpenPopup(null) calls are removed from post-save callbacks across eight list/tab pages. Handlers now chain .then() to refresh the list with current filters and pagination. ShowPagesListPage additionally resets to DEFAULT_CURRENT_PAGE on per-page change and archive toggle.
Test mocks: chain onClose after onSave resolves
src/pages/sponsors/show-pages-list-page/__tests__/show-pages-list-page.test.js, src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/__tests__/sponsor-pages-tab.test.js, src/pages/sponsorship-types/__tests__/sponsorship-list-page.test.js
Mock dialog "Save" buttons updated to call onClose after onSave(...) resolves via .then(onClose), matching the new async close contract in the dialog components.
Action functions: return promises and remove auto-refresh
src/actions/inventory-item-actions.js, src/actions/sponsor-actions.js, src/actions/page-template-actions.js
saveInventoryItem updates its promise handling to propagate errors instead of swallowing them. saveSummitSponsorship now returns the API request promise in both update (putRequest) and create (postRequest) branches. savePageTemplate removes automatic getPageTemplates refresh calls from both update and create success paths, moving the responsibility to parent pages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • fntechgit/summit-admin#764: Both PRs modify src/actions/page-template-actions.js's savePageTemplate flow and its getPageTemplates refresh invocation.
  • fntechgit/summit-admin#841: Both PRs modify src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js around managed/customized page popup save handling and follow-up close/refresh behavior.
  • fntechgit/summit-admin#890: Both PRs modify src/pages/sponsorship-types/components/sponsorship-dialog.js and src/pages/sponsorship-types/sponsorship-list-page.js around internalization of isSaving state and popup closing/refresh wiring.

Suggested reviewers

  • smarcet

Poem

🐰 Hop hop, the dialogs now know when they're done,
No more parent pages closing things on the run!
isSaving lives inside, a local little flag,
Escape and X are locked while the save does drag.
The lists refresh after, neat and precise —
Self-managing popups: oh, isn't that nice! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately summarizes the main changes: adjusting the popup pattern, adding isSaving state management to popups, and ensuring promise returns across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/popup-pattern-deviations

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js (1)

1-1: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing useState import causes ReferenceError.

Line 48 uses useState but it is not imported from React on line 1. This will throw a runtime error when the component renders.

🐛 Proposed fix to add the missing import
-import React from "react";
+import React, { useState } from "react";

Also applies to: 48-49

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js` at line
1, The component uses the useState hook on lines 48-49 but it is not imported in
the React import statement on line 1. Add useState to the destructured import
from React by updating the import statement to include useState alongside React,
so that the useState hook is available when the component renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/sponsors-global/inventory/inventory-list-page.js`:
- Around line 249-259: The `handleInventorySave` function relies on the promise
returned by `saveInventoryItem` to determine when the save operation is truly
complete and whether it succeeded or failed, but the `saveInventoryItem`
function in src/actions/inventory-item-actions.js (around lines 205-300) is
currently swallowing errors and resolving prematurely before nested image and
metadata saves finish. Fix this by ensuring the `saveInventoryItem` function
waits for all nested save operations (image saves and metadata saves) to
complete before resolving, and properly rejects the returned promise if any part
of the save process fails, so that the promise chain accurately reflects the
true completion status and allows error handling in downstream code like
dialog-close logic to respond correctly.

In `@src/pages/sponsors-global/page-templates/page-template-list-page.js`:
- Around line 117-128: The handleSavePageTemplate function does not return a
promise, causing runtime failures when the popup contract attempts to chain
.then on the result. Add a return statement before the savePageTemplate promise
chain to ensure the entire promise returned from
savePageTemplate(entity).then(...) is returned from the handler, allowing
callers to properly chain .then(onClose) on the result.
- Around line 118-127: The savePageTemplate call in the
page-template-list-page.js file has a redundant .then() chain that invokes
getPageTemplates again, causing duplicate requests. Since savePageTemplate
already dispatches getPageTemplates internally, remove the entire .then() block
that calls getPageTemplates with the term, DEFAULT_CURRENT_PAGE, perPage, order,
orderDir, and showArchived parameters. Simply call savePageTemplate(entity)
without the chained getPageTemplates invocation to let the internal dispatch
handle the refresh with the appropriate state values.

In `@src/pages/sponsors/summit-sponsorship-list-page.js`:
- Around line 99-101: The saveSummitSponsorship thunk in
src/actions/sponsor-actions.js does not return the promise from the API request
(putRequest or postRequest), causing the .then() chain in
summit-sponsorship-list-page.js to execute prematurely. Modify the
saveSummitSponsorship function to return the actual promise from the
putRequest/postRequest call so that getSummitSponsorships is only invoked after
the API write completes, preventing the popup from closing before persistence
finishes.

---

Outside diff comments:
In `@src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js`:
- Line 1: The component uses the useState hook on lines 48-49 but it is not
imported in the React import statement on line 1. Add useState to the
destructured import from React by updating the import statement to include
useState alongside React, so that the useState hook is available when the
component renders.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a8515e5-6d59-4f49-a419-d75f0973d2c7

📥 Commits

Reviewing files that changed from the base of the PR and between 23217a9 and 9de307d.

📒 Files selected for processing (21)
  • src/pages/sponsors-global/form-templates/form-template-item-list-page.js
  • src/pages/sponsors-global/form-templates/form-template-list-page.js
  • src/pages/sponsors-global/form-templates/form-template-popup.js
  • src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js
  • src/pages/sponsors-global/inventory/inventory-list-page.js
  • src/pages/sponsors-global/page-templates/page-template-list-page.js
  • src/pages/sponsors-global/page-templates/page-template-popup/index.js
  • src/pages/sponsors/popup/add-sponsor-popup.js
  • src/pages/sponsors/popup/edit-tier-popup.js
  • src/pages/sponsors/show-pages-list-page/__tests__/show-pages-list-page.test.js
  • src/pages/sponsors/show-pages-list-page/index.js
  • src/pages/sponsors/sponsor-list-page.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/__tests__/sponsor-pages-tab.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js
  • src/pages/sponsors/summit-sponsorship-list-page.js
  • src/pages/sponsorship-types/__tests__/sponsorship-list-page.test.js
  • src/pages/sponsorship-types/components/sponsorship-dialog.js
  • src/pages/sponsorship-types/sponsorship-list-page.js
  • src/pages/tags/tag-list-page.js

Comment on lines +249 to +259
const handleInventorySave = (item) =>
saveInventoryItem(item).then(() =>
getInventoryItems(
term,
currentPage,
perPage,
order,
orderDir,
showArchived
)
.finally(() => setOpen(false));
};
);

@coderabbitai coderabbitai Bot Jun 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure the returned save promise reflects real completion/failure.

handleInventorySave now drives post-save flow from the returned promise, but saveInventoryItem (in src/actions/inventory-item-actions.js:205-300) currently swallows errors and resolves before nested image/meta saves finish. This can refresh stale data and let dialog-close logic treat failed saves as success.

Suggested fix in src/actions/inventory-item-actions.js
-      .then(() => {
+      .then(() => {
         const promises = [];
@@
-        Promise.all(promises)
+        return Promise.all(promises)
           .then(() => {
             dispatch(
               showSuccessMessage(
                 T.translate("edit_inventory_item.inventory_item_saved")
               )
             );
           })
-          .catch((err) => {
-            console.error(err);
-          })
           .finally(() => {
             dispatch(stopLoading());
           });
       })
       .catch((err) => {
         console.error(err);
+        throw err;
       });
@@
-    .then(({ response }) => {
+    .then(({ response }) => {
@@
-      Promise.all(promises)
+      return Promise.all(promises)
         .then(() => {
           dispatch(
             showMessage(success_message, () => {
               history.push("/app/inventory");
             })
           );
         })
-        .catch((err) => {
-          console.error(err);
-        })
         .finally(() => {
           dispatch(stopLoading());
         });
     })
     .catch((err) => {
       console.error(err);
+      throw err;
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/sponsors-global/inventory/inventory-list-page.js` around lines 249
- 259, The `handleInventorySave` function relies on the promise returned by
`saveInventoryItem` to determine when the save operation is truly complete and
whether it succeeded or failed, but the `saveInventoryItem` function in
src/actions/inventory-item-actions.js (around lines 205-300) is currently
swallowing errors and resolving prematurely before nested image and metadata
saves finish. Fix this by ensuring the `saveInventoryItem` function waits for
all nested save operations (image saves and metadata saves) to complete before
resolving, and properly rejects the returned promise if any part of the save
process fails, so that the promise chain accurately reflects the true completion
status and allows error handling in downstream code like dialog-close logic to
respond correctly.

✅ Addressed in commit 19a9c72

Comment thread src/pages/sponsors-global/page-templates/page-template-list-page.js Outdated
Comment thread src/pages/sponsors-global/page-templates/page-template-list-page.js
Comment on lines +99 to +101
saveSummitSponsorship(sponsorship).then(() =>
getSummitSponsorships(DEFAULT_CURRENT_PAGE, perPage, order, orderDir)
);

@coderabbitai coderabbitai Bot Jun 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the real save promise before chaining refresh.

Line 99 assumes saveSummitSponsorship resolves after the API write, but the current thunk (src/actions/sponsor-actions.js) does not return the inner putRequest/postRequest promise. This makes the refresh run early and can close the popup before persistence completes.

Suggested fix at the root cause (`src/actions/sponsor-actions.js`)
 export const saveSummitSponsorship = (entity) => async (dispatch, getState) => {
   // ...
   dispatch(startLoading());

   const normalizedEntity = normalizeSponsorship(entity);

   if (entity.id) {
-    putRequest(
+    return putRequest(
       createAction(UPDATE_SUMMIT_SPONSORSHIP),
       createAction(SUMMIT_SPONSORSHIP_UPDATED),
       `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsorships-types/${entity.id}`,
       normalizedEntity,
       authErrorHandler,
       entity
-    )(params)(dispatch).then(() => {
-      dispatch(
-        showSuccessMessage(T.translate("edit_sponsorship.sponsorship_saved"))
-      );
-    });
+    )(params)(dispatch)
+      .then(() => {
+        dispatch(
+          showSuccessMessage(T.translate("edit_sponsorship.sponsorship_saved"))
+        );
+      })
+      .finally(() => {
+        dispatch(stopLoading());
+      });
   } else {
     const success_message = {
       title: T.translate("general.done"),
       html: T.translate("edit_sponsorship.sponsorship_created"),
       type: "success"
     };

-    postRequest(
+    return postRequest(
       createAction(UPDATE_SUMMIT_SPONSORSHIP),
       createAction(SUMMIT_SPONSORSHIP_ADDED),
       `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsorships-types`,
       normalizedEntity,
       authErrorHandler,
       entity
-    )(params)(dispatch).then(() => {
-      dispatch(showMessage(success_message));
-    });
+    )(params)(dispatch)
+      .then(() => {
+        dispatch(showMessage(success_message));
+      })
+      .finally(() => {
+        dispatch(stopLoading());
+      });
   }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/sponsors/summit-sponsorship-list-page.js` around lines 99 - 101,
The saveSummitSponsorship thunk in src/actions/sponsor-actions.js does not
return the promise from the API request (putRequest or postRequest), causing the
.then() chain in summit-sponsorship-list-page.js to execute prematurely. Modify
the saveSummitSponsorship function to return the actual promise from the
putRequest/postRequest call so that getSummitSponsorships is only invoked after
the API write completes, preventing the popup from closing before persistence
finishes.

✅ Addressed in commit 19a9c72

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/actions/inventory-item-actions.js`:
- Around line 236-250: The stopLoading() dispatch call inside the inner
.finally() block (after Promise.all) only executes when the putRequest succeeds.
If the putRequest fails, the outer catch block rethrows the error without ever
clearing the loading state. Move the stopLoading() call from the inner
.finally() block to an outer .finally() block at the end of the entire promise
chain, so it executes regardless of whether the putRequest or subsequent
Promise.all operations succeed or fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 506f0181-cdcb-423b-a046-3729b1f92efb

📥 Commits

Reviewing files that changed from the base of the PR and between 9de307d and 19a9c72.

📒 Files selected for processing (4)
  • src/actions/inventory-item-actions.js
  • src/actions/page-template-actions.js
  • src/actions/sponsor-actions.js
  • src/pages/sponsors-global/page-templates/page-template-list-page.js
💤 Files with no reviewable changes (1)
  • src/actions/page-template-actions.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/pages/sponsors-global/page-templates/page-template-list-page.js

Comment thread src/actions/inventory-item-actions.js Outdated
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom tomrndom requested a review from smarcet June 19, 2026 21:22

const handleSaveSummitSponsorship = (sponsorship) => {
saveSummitSponsorship(sponsorship).then(() => setShowAddTierModal(false));
saveSummitSponsorship(sponsorship).then(() =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom handleSaveSummitSponsorship doesn't return the promise chain (no return on the arrow function body), but EditTierDialog's new onSubmit (edit-tier-popup.js) does onSubmit(values).then(() => onClose()).finally(...). Since onSubmit(values) here evaluates to undefined, that .then throws a TypeError - the save itself still fires, but the dialog never closes and isSaving never resets (Save/Close/Escape stay disabled until the page is reloaded). Every other handler converted in this PR uses the implicit-return arrow form; this one was left as a block body - compare to handleNewSponsor in sponsor-list-page.js for the correct shape. Per the popup pattern doc (https://github.com/fntechgit/ftn-docsnsklz/blob/main/patterns/show-admin/summit-admin-popup-dialog-pattern.md), the parent's save handler must return the save promise.

})
.catch((err) => {
console.error(err);
throw err;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom This PR fixes the update branch here to return/rethrow on failure, but the sibling create branch below (postRequest(...), ~line 259) still has the same bug this fix addresses: the inner Promise.all(promises)...catch(err => console.error(err)) is never returned, and the outer .catch((err) => { console.error(err); }) doesn't rethrow - so saveInventoryItem for a new item always resolves, even when the POST fails. Combined with the new self-close contract in sponsor-inventory-popup.js (onSave(values).then(() => onClose())), a failed "Add Item" now silently closes the dialog as if it succeeded. Worth applying the same fix to the create branch.

orderDir,
showArchived
)
const handleInventorySave = (item) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom Per the popup pattern doc's Responsibility Split section (https://github.com/fntechgit/ftn-docsnsklz/blob/main/patterns/show-admin/summit-admin-popup-dialog-pattern.md), the list refresh inside the parent's save handler should be fire-and-forget (getMyEntities(...).catch(() => {})) so a refresh failure can't reject the promise the popup is waiting on to close. Here - and in every other handler converted by this PR (handleFormTemplateSave, handleOnSave in form-template-list-page.js, handleSavePageTemplate, handleSaveShowPage, handleNewSponsor, handleItemSave, the three handlers in sponsor-pages-tab/index.js, handleSave in sponsorship-list-page.js, handleSaveTag) - the refresh call isn't wrapped in its own .catch(). None of the underlying list actions (getInventoryItems, getFormTemplates, getSummitSponsorships, etc.) swallow their own errors either, so a transient refresh failure right after a successful save currently rejects the whole chain and leaves the dialog open - exactly the scenario the doc calls out.

onSubmit: (values) => {
if (isSaving) return;
setIsSaving(true);
onSave(values)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom This dialog (and page-template-popup/index.js, add-sponsor-popup.js, edit-tier-popup.js, add-sponsor-page-template-popup/index.js, sponsorship-dialog.js) is missing the .catch(() => {}) between .then(() => onClose()) and .finally(...). form-template-popup.js has it and is the only one of the seven dialogs that matches the doc's Save Guard Pattern checklist item "Dialog stays open on save error (.catch)" (https://github.com/fntechgit/ftn-docsnsklz/blob/main/patterns/show-admin/summit-admin-popup-dialog-pattern.md). Functionally the dialog still stays open on a rejected save (since .then just doesn't fire), but the rejection goes unhandled - console noise and potential false positives in error monitoring on every failed save across 6 of 7 dialogs.


setIsSaving(true);
Promise.resolve(onSave(finalValues))
onSave(finalValues)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom This dialog still takes an open prop (line 30 / <Dialog open={open}>) and its parent (form-template-list-page.js) still renders it unconditionally with open={formTemplatePopupOpen}, instead of the conditional-mount pattern ({formTemplatePopupOpen && <FormTemplateDialog .../>}, no open prop) that every other dialog in this PR was migrated to. Per the doc (https://github.com/fntechgit/ftn-docsnsklz/blob/main/patterns/show-admin/summit-admin-popup-dialog-pattern.md), always-render is explicitly called out as the anti-pattern to avoid, since it requires manually resetting internal state instead of getting it for free from unmount/remount. Not an active bug today (the catch/finally here is complete and enableReinitialize covers reopening for a different entity), but it's now the one outlier relative to the rest of the PR's own convention.

@smarcet smarcet requested a review from Copilot July 8, 2026 17:52

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom please review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes “popup/dialog save” behavior across the admin UI by having save handlers return promises, moving “isSaving” state into the popup components to prevent duplicate submissions/closure during saves, and refreshing lists after successful saves.

Changes:

  • Refactored multiple page-level handleSave* functions to return promise chains and refresh list data after saves.
  • Updated several dialogs/popups to manage isSaving locally and disable close/escape/save while saving.
  • Updated tests and selected Redux actions to better support async save flows (including improved error propagation for inventory saves).

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/pages/tags/tag-list-page.js Returns the refresh promise from tag save flow.
src/pages/sponsorship-types/sponsorship-list-page.js Removes page-level isSaving; save handler now returns a promise for dialog-managed closing.
src/pages/sponsorship-types/components/sponsorship-dialog.js Adds local isSaving gating and closes only after save promise resolves.
src/pages/sponsorship-types/tests/sponsorship-list-page.test.js Updates mocked save to match new promise-based close flow.
src/pages/sponsors/summit-sponsorship-list-page.js Refreshes list after tier save and renders popup conditionally (no open prop).
src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/index.js Refactors multiple save handlers to return promises and refresh lists.
src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js Adds isSaving and disables close interactions while saving; closes on fulfilled save.
src/pages/sponsors/sponsor-page/tabs/sponsor-pages-tab/tests/sponsor-pages-tab.test.js Updates mocked save to match promise-based close flow.
src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js Returns promise from item save so the inventory dialog can close itself on success.
src/pages/sponsors/sponsor-list-page.js Removes open prop usage and returns promise from add-sponsor save flow.
src/pages/sponsors/show-pages-list-page/index.js Resets pagination on per-page/archive changes; returns save promise and refreshes list with current filters.
src/pages/sponsors/show-pages-list-page/tests/show-pages-list-page.test.js Updates mocked save to match promise-based close flow.
src/pages/sponsors/popup/edit-tier-popup.js Moves isSaving into dialog; disables close interactions and closes on fulfilled save.
src/pages/sponsors/popup/add-sponsor-popup.js Moves isSaving into dialog; disables close interactions and closes on fulfilled save.
src/pages/sponsors-global/page-templates/page-template-popup/index.js Adds isSaving UX and closes on fulfilled save.
src/pages/sponsors-global/page-templates/page-template-list-page.js Refreshes page templates list after save instead of closing immediately.
src/pages/sponsors-global/inventory/inventory-list-page.js Returns promise chain from inventory save and refreshes list after save.
src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js Adds isSaving UX and closes on fulfilled save.
src/pages/sponsors-global/form-templates/form-template-popup.js Adjusts save flow to rely on promise-returning onSave.
src/pages/sponsors-global/form-templates/form-template-list-page.js Refactors save handler to return promise and refresh list.
src/pages/sponsors-global/form-templates/form-template-item-list-page.js Refactors item save handler to return promise and refresh list.
src/actions/sponsor-actions.js Ensures saveSummitSponsorship returns request promises.
src/actions/page-template-actions.js Removes auto-refresh side effect from action (callers now refresh).
src/actions/inventory-item-actions.js Improves error propagation by rethrowing after logging and returning the inner promise chain.
Comments suppressed due to low confidence (2)

src/actions/page-template-actions.js:180

  • This catch logs the error but resolves the returned promise. Since popups now close only on fulfilled saves (via .then(() => onClose())), swallowing the rejection will cause the UI to close even when the API call fails. Re-throw after logging so callers can keep the dialog open on failure.
        );
      })
      .catch((err) => {
        console.error(err);
      })

src/actions/page-template-actions.js:204

  • Same as above: this catch swallows errors, which will make callers treat a failed create as success and close the popup. Re-throw so the promise rejects on failure.
      );
    })
    .catch((err) => {
      console.error(err);
    })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

onMetaFieldTypeValueDeleted,
entity: initialEntity
}) => {
const [isSaving, setIsSaving] = useState(false);
Comment on lines 132 to +134
}),
enableReinitialize: true,
onSubmit: (values) => {
if (isSaving) return;
Comment on lines +1141 to 1143
}
const success_message = {
title: T.translate("general.done"),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants