From df6165629684afd38ff817eb313e500829c2fb3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Wed, 1 Jul 2026 20:37:14 -0300 Subject: [PATCH 01/12] fix: add missing file remove calls from inventory popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 21 +++++++++++++++++++ .../form-template-item-list-page.js | 6 +++++- .../form-templates/sponsor-inventory-popup.js | 7 +++++++ .../inventory/inventory-list-page.js | 7 +++++-- .../components/sponsor-form-item-form.js | 7 ++++++- .../components/sponsor-form-item-popup.js | 13 +++++++++--- .../sponsor-forms-manage-items.js | 5 +++++ 7 files changed, 59 insertions(+), 7 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 5492628e7..045f35465 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -107,6 +107,7 @@ export const RESET_SPONSOR_FORM_ITEM = "RESET_SPONSOR_FORM_ITEM"; export const SPONSOR_FORM_ITEM_DELETED = "SPONSOR_FORM_ITEM_DELETED"; export const SPONSOR_FORM_ITEM_IMAGES_UPDATED = "SPONSOR_FORM_ITEM_IMAGES_UPDATED"; +export const SPONSOR_FORM_ITEM_FILE_DELETED = "SPONSOR_FORM_ITEM_FILE_DELETED"; export const SPONSOR_FORM_ITEMS_ADDED = "SPONSOR_FORM_ITEMS_ADDED"; export const SPONSOR_FORM_ITEM_ARCHIVED = "SPONSOR_FORM_ITEM_ARCHIVED"; export const SPONSOR_FORM_ITEM_UNARCHIVED = "SPONSOR_FORM_ITEM_UNARCHIVED"; @@ -1262,6 +1263,26 @@ const saveItemImages = return Promise.all(promises); }; +export const removeItemFile = + (formId, formItemId, fileId) => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + dispatch(startLoading()); + + return deleteRequest( + null, + createAction(SPONSOR_FORM_ITEM_FILE_DELETED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images/${fileId}`, + null, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; + export const saveSponsorFormItem = (formId, entity) => async (dispatch, getState) => { const { currentSummitState } = getState(); diff --git a/src/pages/sponsors-global/form-templates/form-template-item-list-page.js b/src/pages/sponsors-global/form-templates/form-template-item-list-page.js index 1acff070a..d0fe99db9 100644 --- a/src/pages/sponsors-global/form-templates/form-template-item-list-page.js +++ b/src/pages/sponsors-global/form-templates/form-template-item-list-page.js @@ -187,6 +187,10 @@ const FormTemplateItemListPage = ({ ).catch(() => {}) ); + const handleRemoveImage = (imageId) => { + deleteItemImage(formTemplateId, currentFormTemplateItem.id, imageId); + }; + const columns = [ { columnKey: "code", @@ -319,7 +323,7 @@ const FormTemplateItemListPage = ({ onClose={() => setShowInventoryItemModal(false)} onMetaFieldTypeDeleted={deleteItemMetaFieldType} onMetaFieldTypeValueDeleted={deleteItemMetaFieldTypeValue} - onImageDeleted={deleteItemImage} + onImageDeleted={handleRemoveImage} /> )} diff --git a/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js index 72cb512cb..e56875dd5 100644 --- a/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js +++ b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js @@ -43,6 +43,7 @@ const SponsorItemDialog = ({ onSave, onMetaFieldTypeDeleted, onMetaFieldTypeValueDeleted, + onImageDeleted, entity: initialEntity }) => { const [isSaving, setIsSaving] = useState(false); @@ -93,6 +94,10 @@ const SponsorItemDialog = ({ onClose(); }; + const handleIDeleteImage = (id) => { + if (id) onImageDeleted(id); + }; + return ( @@ -263,6 +269,7 @@ SponsorItemDialog.propTypes = { onSave: PropTypes.func.isRequired, onMetaFieldTypeDeleted: PropTypes.func, onMetaFieldTypeValueDeleted: PropTypes.func, + onImageDeleted: PropTypes.func, entity: PropTypes.object }; diff --git a/src/pages/sponsors-global/inventory/inventory-list-page.js b/src/pages/sponsors-global/inventory/inventory-list-page.js index a3ba172f5..662b55024 100644 --- a/src/pages/sponsors-global/inventory/inventory-list-page.js +++ b/src/pages/sponsors-global/inventory/inventory-list-page.js @@ -149,6 +149,10 @@ const InventoryListPage = ({ ? unarchiveInventoryItem(item) : archiveInventoryItem(item); + const handleRemoveImage = (imageId) => { + deleteInventoryItemImage(currentInventoryItem.id, imageId); + }; + const columns = [ { columnKey: "code", @@ -291,7 +295,7 @@ const InventoryListPage = ({ onClose={handleClose} onMetaFieldTypeDeleted={deleteInventoryItemMetaFieldType} onMetaFieldTypeValueDeleted={deleteInventoryItemMetaFieldTypeValue} - onImageDeleted={deleteInventoryItemImage} + onImageDeleted={handleRemoveImage} /> )} @@ -312,7 +316,6 @@ export default connect(mapStateToProps, { getInventoryItem, resetInventoryItemForm, saveInventoryItem, - deleteInventoryItemImage, deleteInventoryItemMetaFieldType, deleteInventoryItemMetaFieldTypeValue, diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js index 26abd82d4..769e4253e 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js @@ -34,7 +34,7 @@ const buildInitialValues = (data) => ({ ...data }); addIssAfterDateFieldValidator(); -const SponsorFormItemForm = ({ initialValues, onSubmit, isSaving }) => { +const SponsorFormItemForm = ({ initialValues, onSubmit, isSaving, onImageDeleted }) => { const formik = useFormik({ initialValues: buildInitialValues(initialValues), validationSchema: yup.object({ @@ -57,6 +57,10 @@ const SponsorFormItemForm = ({ initialValues, onSubmit, isSaving }) => { // SCROLL TO ERROR useScrollToError(formik); + const handleDeleteImage = (id) => { + if (id) onImageDeleted(id); + }; + return ( { id="item-image-upload" name="images" maxFiles={MAX_INVENTORY_IMAGES_UPLOAD_QTY} + onDelete={handleDeleteImage} allowedExtensions={getFileUploadAllowedExtensions()} /> diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js index b908a5c4d..fe0b73f0c 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js @@ -11,7 +11,7 @@ import { import CloseIcon from "@mui/icons-material/Close"; import SponsorFormItemForm from "./sponsor-form-item-form"; -const SponsorFormItemPopup = ({ item, onClose, onSave }) => { +const SponsorFormItemPopup = ({ item, onClose, onSave, onRemoveImage }) => { const [isSaving, setIsSaving] = useState(false); const handleClose = () => { @@ -24,10 +24,14 @@ const SponsorFormItemPopup = ({ item, onClose, onSave }) => { setIsSaving(true); onSave(values) .then(() => onClose()) - .catch(() => {}) + .catch(() => { }) .finally(() => setIsSaving(false)); }; + const handleRemoveImage = (imageId) => { + onRemoveImage(imageId); + }; + return ( { {T.translate( `sponsor_form_item_list.edit_item.${item?.id ? "edit" : "new"}` - )} + )}{" "} + laksjdlkajsdklajds { initialValues={item} onSubmit={handleOnSave} isSaving={isSaving} + onImageDeleted={handleRemoveImage} /> ); @@ -67,6 +73,7 @@ const SponsorFormItemPopup = ({ item, onClose, onSave }) => { SponsorFormItemPopup.propTypes = { onClose: PropTypes.func.isRequired, onSave: PropTypes.func.isRequired, + onRemoveImage: PropTypes.func.isRequired, item: PropTypes.object }; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js index 86a48eae5..cad4a61d1 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js @@ -200,6 +200,10 @@ const SponsorFormsManageItems = ({ ); }; + const handleImageRemove = (imageId) => { + console.log("CHECK IMAGE ", imageId); + }; + const sponsorItemColumns = [ { columnKey: "code", @@ -382,6 +386,7 @@ const SponsorFormsManageItems = ({ entity={currentInventoryItem} onSave={handleItemSave} onClose={handleClose} + onImageDeleted={handleImageRemove} /> )} From baa2c59c89c77a86756dbb191ac4f68f3a359145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 9 Jul 2026 09:26:35 -0300 Subject: [PATCH 02/12] fix: add managed form item image remove MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 35 ++++++++++++++++--- .../sponsor-forms-manage-items.js | 15 +++++--- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 045f35465..91ed50524 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -850,7 +850,8 @@ export const getSponsorCustomizedFormItems = const params = { page, per_page: perPage, - access_token: accessToken + access_token: accessToken, + expand: "images" }; filter.push(`is_archived==${showArchived ? 1 : 0}`); @@ -1575,9 +1576,12 @@ export const saveSponsorFormManagedItem = snackbarErrorHandler, entity )(params)(dispatch) - .then(() => { - dispatch(snackbarSuccessHandler(successMessage)); - }) + .then(({ response }) => + saveImages(response.id).then(() => { + dispatch(snackbarSuccessHandler(successMessage)); + return response; + }) + ) .finally(() => { dispatch(stopLoading()); }); @@ -1773,3 +1777,26 @@ export const unarchiveSponsorCustomizedFormItem = .catch(() => {}) .finally(() => dispatch(stopLoading())); }; + +export const removeSponsorCustomizedFormItemImages = + (formId, formItemId, fileId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { + entity: { id: sponsorId } + } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + dispatch(startLoading()); + + return deleteRequest( + null, + createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_DELETED), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${formItemId}/images/${fileId}`, + null, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js index cad4a61d1..0d6fd64fe 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js @@ -36,7 +36,8 @@ import { deleteSponsorFormManagedItem, resetSponsorFormManagedItem, unarchiveSponsorCustomizedFormItem, - getSponsorFormManagedItem + getSponsorFormManagedItem, + removeSponsorCustomizedFormItemImages } from "../../../../../../../actions/sponsor-forms-actions"; import CustomAlert from "../../../../../../../components/mui/custom-alert"; import SponsorInventoryDialog from "../../../../../../sponsors-global/form-templates/sponsor-inventory-popup"; @@ -67,7 +68,8 @@ const SponsorFormsManageItems = ({ deleteSponsorFormManagedItem, archiveSponsorCustomizedFormItem, unarchiveSponsorCustomizedFormItem, - getSponsorFormManagedItem + getSponsorFormManagedItem, + removeSponsorCustomizedFormItemImages }) => { const [openPopup, setOpenPopup] = useState(null); @@ -201,7 +203,11 @@ const SponsorFormsManageItems = ({ }; const handleImageRemove = (imageId) => { - console.log("CHECK IMAGE ", imageId); + removeSponsorCustomizedFormItemImages( + formId, + currentInventoryItem.id, + imageId + ); }; const sponsorItemColumns = [ @@ -414,7 +420,8 @@ export default Restrict( deleteSponsorFormManagedItem, getSponsorFormManagedItem, archiveSponsorCustomizedFormItem, - unarchiveSponsorCustomizedFormItem + unarchiveSponsorCustomizedFormItem, + removeSponsorCustomizedFormItemImages })(SponsorFormsManageItems), ACCESS_ROUTES.ADMIN_SPONSORS ); From 09ad58dc2e4dd3ca664928d7a68e220199f13ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 9 Jul 2026 09:53:29 -0300 Subject: [PATCH 03/12] fix: add catchs, clean code, new action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 5 +++-- .../form-templates/sponsor-inventory-popup.js | 2 +- .../components/sponsor-form-item-form.js | 2 +- .../components/sponsor-form-item-popup.js | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 91ed50524..a1011d397 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -97,7 +97,8 @@ export const SPONSOR_CUSTOMIZED_FORM_ITEMS_ADDED = "SPONSOR_CUSTOMIZED_FORM_ITEMS_ADDED"; export const RESET_SPONSOR_FORM_MANAGED_ITEM = "RESET_SPONSOR_FORM_MANAGED_ITEM"; - +export const SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED = + "SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED"; // ITEMS export const REQUEST_SPONSOR_FORM_ITEMS = "REQUEST_SPONSOR_FORM_ITEMS"; export const RECEIVE_SPONSOR_FORM_ITEMS = "RECEIVE_SPONSOR_FORM_ITEMS"; @@ -1792,7 +1793,7 @@ export const removeSponsorCustomizedFormItemImages = return deleteRequest( null, - createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_DELETED), + createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${formItemId}/images/${fileId}`, null, snackbarErrorHandler diff --git a/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js index e56875dd5..2aa82489e 100644 --- a/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js +++ b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js @@ -95,7 +95,7 @@ const SponsorItemDialog = ({ }; const handleIDeleteImage = (id) => { - if (id) onImageDeleted(id); + if (id && onImageDeleted) onImageDeleted(id); }; return ( diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js index 769e4253e..967ce3ca6 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js @@ -58,7 +58,7 @@ const SponsorFormItemForm = ({ initialValues, onSubmit, isSaving, onImageDeleted useScrollToError(formik); const handleDeleteImage = (id) => { - if (id) onImageDeleted(id); + if (id && onImageDeleted) onImageDeleted(id); }; return ( diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js index fe0b73f0c..b0d36d881 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js @@ -48,7 +48,6 @@ const SponsorFormItemPopup = ({ item, onClose, onSave, onRemoveImage }) => { {T.translate( `sponsor_form_item_list.edit_item.${item?.id ? "edit" : "new"}` )}{" "} - laksjdlkajsdklajds Date: Thu, 9 Jul 2026 09:55:18 -0300 Subject: [PATCH 04/12] fix: clean blank space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../components/sponsor-form-item-popup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js index b0d36d881..4d2be9764 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js @@ -47,7 +47,7 @@ const SponsorFormItemPopup = ({ item, onClose, onSave, onRemoveImage }) => { {T.translate( `sponsor_form_item_list.edit_item.${item?.id ? "edit" : "new"}` - )}{" "} + )} Date: Sat, 1 Aug 2026 02:36:18 -0300 Subject: [PATCH 05/12] fix: remove undefined function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index a1011d397..c2c1cc66a 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -1577,12 +1577,9 @@ export const saveSponsorFormManagedItem = snackbarErrorHandler, entity )(params)(dispatch) - .then(({ response }) => - saveImages(response.id).then(() => { - dispatch(snackbarSuccessHandler(successMessage)); - return response; - }) - ) + .then(() => { + dispatch(snackbarSuccessHandler(successMessage)); + }) .finally(() => { dispatch(stopLoading()); }); From 564071b8bf763d17a6cdc923ed73ad9fe723ec57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Sat, 1 Aug 2026 02:45:33 -0300 Subject: [PATCH 06/12] fix: adjust image delete with current popup structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsors/sponsor-form-item-list-page/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/sponsors/sponsor-form-item-list-page/index.js b/src/pages/sponsors/sponsor-form-item-list-page/index.js index 260924bd8..ae69d28f5 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/index.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js @@ -36,7 +36,8 @@ import { addInventoryItems, resetSponsorFormItem, archiveSponsorFormItem, - unarchiveSponsorFormItem + unarchiveSponsorFormItem, + removeItemFile } from "../../../actions/sponsor-forms-actions"; import { getInventoryItems } from "../../../actions/inventory-item-actions"; import SponsorFormItemPopup from "./components/sponsor-form-item-popup"; @@ -65,7 +66,8 @@ const SponsorFormItemListPage = ({ addInventoryItems, resetSponsorFormItem, archiveSponsorFormItem, - unarchiveSponsorFormItem + unarchiveSponsorFormItem, + removeItemFile }) => { const [openPopup, setOpenPopup] = useState(null); const { form_id: formId } = match.params; @@ -129,6 +131,10 @@ const SponsorFormItemListPage = ({ ); }; + const handleRemoveItemImage = (imageId) => { + removeItemFile(formId, currentItem, imageId); + }; + const handleAddFromInventory = (itemIds) => addInventoryItems(formId, itemIds).then(() => getSponsorFormItems( @@ -359,6 +365,7 @@ const SponsorFormItemListPage = ({ item={currentItem} onSave={handleSaveItem} onClose={handleClosePopup} + onRemoveImage={handleRemoveItemImage} /> )} {openPopup === "inventory" && ( @@ -391,5 +398,6 @@ export default connect(mapStateToProps, { resetSponsorFormItem, getInventoryItems, archiveSponsorFormItem, - unarchiveSponsorFormItem + unarchiveSponsorFormItem, + removeItemFile })(SponsorFormItemListPage); From bd8aa78a9abc20a8a37886cdd18d8dc6c74528da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Sun, 2 Aug 2026 21:02:59 -0300 Subject: [PATCH 07/12] fix: adjust reducer, pass currentItem id on remove image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-forms-actions.js | 2 +- .../sponsor-form-item-list-page/index.js | 5 ++--- .../sponsors/sponsor-form-items-list-reducer.js | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index c2c1cc66a..b82612f47 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -1276,7 +1276,7 @@ export const removeItemFile = return deleteRequest( null, - createAction(SPONSOR_FORM_ITEM_FILE_DELETED), + createAction(SPONSOR_FORM_ITEM_FILE_DELETED)({ fileId }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images/${fileId}`, null, snackbarErrorHandler diff --git a/src/pages/sponsors/sponsor-form-item-list-page/index.js b/src/pages/sponsors/sponsor-form-item-list-page/index.js index ae69d28f5..39cce493b 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/index.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js @@ -131,9 +131,8 @@ const SponsorFormItemListPage = ({ ); }; - const handleRemoveItemImage = (imageId) => { - removeItemFile(formId, currentItem, imageId); - }; + const handleRemoveItemImage = (imageId) => + removeItemFile(formId, currentItem?.id, imageId); const handleAddFromInventory = (itemIds) => addInventoryItems(formId, itemIds).then(() => diff --git a/src/reducers/sponsors/sponsor-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-form-items-list-reducer.js index 21e0c7597..1bb26338d 100644 --- a/src/reducers/sponsors/sponsor-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-form-items-list-reducer.js @@ -20,6 +20,7 @@ import { RESET_SPONSOR_FORM_ITEM, SPONSOR_FORM_ITEM_ARCHIVED, SPONSOR_FORM_ITEM_DELETED, + SPONSOR_FORM_ITEM_FILE_DELETED, SPONSOR_FORM_ITEM_UNARCHIVED } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; @@ -116,6 +117,21 @@ const sponsorFormItemsListReducer = (state = DEFAULT_STATE, action) => { return { ...state, items }; } + case SPONSOR_FORM_ITEM_FILE_DELETED: { + const { fileId } = payload; + const currentItem = { + ...state.currentItem, + images: state.currentItem.images.filter((img) => img.id !== fileId) + }; + + const items = state.items.map((item) => + item.id === currentItem.id + ? { ...item, images: item.images?.filter((img) => img.id !== fileId) } + : item + ); + + return { ...state, currentItem, items }; + } case SPONSOR_FORM_ITEM_ARCHIVED: { const { id: itemId } = payload.response; const { totalCount, perPage, currentPage } = state; From f5d60799eb84cebec603e4b217d898078953d493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 3 Aug 2026 23:52:58 -0300 Subject: [PATCH 08/12] fix: add unit tests, add case for SPONSOR_FORM_ITEM_FILE_DELETED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/sponsor-forms-actions.test.js | 109 +++++++++++++++++- src/actions/sponsor-forms-actions.js | 7 +- ...customized-form-items-list-reducer.test.js | 42 +++++++ .../sponsor-form-items-list-reducer.test.js | 55 +++++++++ ...nsor-customized-form-items-list-reducer.js | 17 ++- .../sponsor-form-items-list-reducer.js | 17 ++- 6 files changed, 235 insertions(+), 12 deletions(-) diff --git a/src/actions/__tests__/sponsor-forms-actions.test.js b/src/actions/__tests__/sponsor-forms-actions.test.js index 34bb57728..05ac03b63 100644 --- a/src/actions/__tests__/sponsor-forms-actions.test.js +++ b/src/actions/__tests__/sponsor-forms-actions.test.js @@ -6,13 +6,16 @@ import thunk from "redux-thunk"; import flushPromises from "flush-promises"; import { getRequest, - putRequest + putRequest, + deleteRequest } from "openstack-uicore-foundation/lib/utils/actions"; import { getSponsorForms, normalizeFormTemplate, normalizeSponsorCustomizedForm, - updateFormTemplateTiers + updateFormTemplateTiers, + removeItemFile, + removeSponsorCustomizedFormItemImages } from "../sponsor-forms-actions"; import * as methods from "../../utils/methods"; @@ -21,7 +24,8 @@ jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), postRequest: jest.fn(), getRequest: jest.fn(), - putRequest: jest.fn() + putRequest: jest.fn(), + deleteRequest: jest.fn() })); describe("Sponsor Forms Actions", () => { @@ -288,4 +292,103 @@ describe("Sponsor Forms Actions", () => { ); }); }); + + describe("removeItemFile", () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + beforeEach(() => { + jest.spyOn(methods, "getAccessTokenSafely").mockReturnValue("TOKEN"); + + deleteRequest.mockImplementation( + (requestActionCreator, receiveAction) => () => (dispatch) => { + if (typeof receiveAction === "function") { + dispatch(receiveAction({ response: {} })); + } else { + dispatch(receiveAction); + } + return Promise.resolve({ response: {} }); + } + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("dispatches SPONSOR_FORM_ITEM_FILE_DELETED with fileId and itemId", async () => { + const store = mockStore({ + currentSummitState: { currentSummit: { id: 42 } } + }); + + store.dispatch(removeItemFile(7, 99, 555)); + await flushPromises(); + + expect(deleteRequest).toHaveBeenCalledWith( + null, + { + type: "SPONSOR_FORM_ITEM_FILE_DELETED", + payload: { fileId: 555, itemId: 99 } + }, + `${window.PURCHASES_API_URL}/api/v1/summits/42/show-forms/7/items/99/images/555`, + null, + expect.any(Function) + ); + + const dispatched = store + .getActions() + .find((a) => a.type === "SPONSOR_FORM_ITEM_FILE_DELETED"); + expect(dispatched.payload).toEqual({ fileId: 555, itemId: 99 }); + }); + }); + + describe("removeSponsorCustomizedFormItemImages", () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + beforeEach(() => { + jest.spyOn(methods, "getAccessTokenSafely").mockReturnValue("TOKEN"); + + deleteRequest.mockImplementation( + (requestActionCreator, receiveAction) => () => (dispatch) => { + if (typeof receiveAction === "function") { + dispatch(receiveAction({ response: {} })); + } else { + dispatch(receiveAction); + } + return Promise.resolve({ response: {} }); + } + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("dispatches SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED with fileId", async () => { + const store = mockStore({ + currentSummitState: { currentSummit: { id: 42 } }, + currentSponsorState: { entity: { id: 5 } } + }); + + store.dispatch(removeSponsorCustomizedFormItemImages(7, 99, 555)); + await flushPromises(); + + expect(deleteRequest).toHaveBeenCalledWith( + null, + { + type: "SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED", + payload: { fileId: 555 } + }, + `${window.PURCHASES_API_URL}/api/v1/summits/42/sponsors/5/sponsor-forms/7/items/99/images/555`, + null, + expect.any(Function) + ); + + const dispatched = store + .getActions() + .find((a) => a.type === "SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED"); + expect(dispatched.payload).toEqual({ fileId: 555 }); + }); + }); }); diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index b82612f47..955bb27f2 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -1276,7 +1276,10 @@ export const removeItemFile = return deleteRequest( null, - createAction(SPONSOR_FORM_ITEM_FILE_DELETED)({ fileId }), + createAction(SPONSOR_FORM_ITEM_FILE_DELETED)({ + fileId, + itemId: formItemId + }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images/${fileId}`, null, snackbarErrorHandler @@ -1790,7 +1793,7 @@ export const removeSponsorCustomizedFormItemImages = return deleteRequest( null, - createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED), + createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED)({ fileId }), `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${formItemId}/images/${fileId}`, null, snackbarErrorHandler diff --git a/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js index 23ac56ae5..4187aad1c 100644 --- a/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js @@ -1,6 +1,7 @@ import sponsorCustomizedFormItemsListReducer from "../sponsor-customized-form-items-list-reducer"; import { RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM, + SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, SPONSOR_FORM_MANAGED_ITEM_UPDATED } from "../../../actions/sponsor-forms-actions"; @@ -92,6 +93,47 @@ describe("sponsorCustomizedFormItemsListReducer", () => { }); }); + describe("SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED", () => { + it("removes the image from currentItem and its matching list item", () => { + const state = { + ...DEFAULT_STATE, + currentItem: { + ...DEFAULT_STATE.currentItem, + id: 1, + images: [{ id: 10 }, { id: 11 }] + }, + items: [ + buildItem({ id: 1, images: [{ id: 10 }, { id: 11 }] }), + buildItem({ id: 2, images: [{ id: 12 }] }) + ] + }; + + const result = sponsorCustomizedFormItemsListReducer(state, { + type: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, + payload: { fileId: 10 } + }); + + expect(result.currentItem.images).toEqual([{ id: 11 }]); + expect(result.items[0].images).toEqual([{ id: 11 }]); + expect(result.items[1].images).toEqual([{ id: 12 }]); + }); + + it("handles a currentItem with no images without throwing", () => { + const state = { + ...DEFAULT_STATE, + currentItem: { ...DEFAULT_STATE.currentItem, id: 1, images: undefined }, + items: [buildItem({ id: 1, images: undefined })] + }; + + const result = sponsorCustomizedFormItemsListReducer(state, { + type: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, + payload: { fileId: 10 } + }); + + expect(result.currentItem.images).toEqual([]); + }); + }); + describe("SPONSOR_FORM_MANAGED_ITEM_UPDATED", () => { it("replaces the matching list item and preserves its images as-is", () => { const images = [{ id: 20, file_url: "https://cdn/img.png" }]; diff --git a/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js index 054db2924..2a6dfefc7 100644 --- a/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js @@ -8,6 +8,7 @@ import { RESET_SPONSOR_FORM_ITEM, SPONSOR_FORM_ITEM_ARCHIVED, SPONSOR_FORM_ITEM_DELETED, + SPONSOR_FORM_ITEM_FILE_DELETED, SPONSOR_FORM_ITEM_UNARCHIVED } from "../../../actions/sponsor-forms-actions"; @@ -268,6 +269,60 @@ describe("SponsorFormItemsListReducer", () => { }); }); + describe("SPONSOR_FORM_ITEM_FILE_DELETED", () => { + it("removes the image from currentItem and its matching list item", () => { + const state = { + ...initialState, + currentItem: { + ...initialState.currentItem, + id: "A", + images: [{ id: "IMG_1" }, { id: "IMG_2" }] + }, + items: [ + { id: "A", images: [{ id: "IMG_1" }, { id: "IMG_2" }] }, + { id: "B", images: [{ id: "IMG_3" }] } + ] + }; + + result = SponsorFormItemsListReducer(state, { + type: SPONSOR_FORM_ITEM_FILE_DELETED, + payload: { fileId: "IMG_1", itemId: "A" } + }); + + expect(result.currentItem.images).toStrictEqual([{ id: "IMG_2" }]); + expect(result.items).toStrictEqual([ + { id: "A", images: [{ id: "IMG_2" }] }, + { id: "B", images: [{ id: "IMG_3" }] } + ]); + }); + + it("leaves currentItem untouched when the deleted file belongs to a different item", () => { + const state = { + ...initialState, + currentItem: { + ...initialState.currentItem, + id: "B", + images: [{ id: "IMG_3" }] + }, + items: [ + { id: "A", images: [{ id: "IMG_1" }] }, + { id: "B", images: [{ id: "IMG_3" }] } + ] + }; + + result = SponsorFormItemsListReducer(state, { + type: SPONSOR_FORM_ITEM_FILE_DELETED, + payload: { fileId: "IMG_1", itemId: "A" } + }); + + expect(result.currentItem).toStrictEqual(state.currentItem); + expect(result.items).toStrictEqual([ + { id: "A", images: [] }, + { id: "B", images: [{ id: "IMG_3" }] } + ]); + }); + }); + describe("SPONSOR_FORM_ITEM_ARCHIVED", () => { it("execution", () => { result = SponsorFormItemsListReducer( diff --git a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js index 84c58770f..e835ff6c8 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -22,7 +22,8 @@ import { SPONSOR_CUSTOMIZED_FORM_ITEM_UNARCHIVED, SPONSOR_FORM_MANAGED_ITEM_UPDATED, SPONSOR_CUSTOMIZED_FORM_ITEMS_ADDED, - RESET_SPONSOR_FORM_MANAGED_ITEM + RESET_SPONSOR_FORM_MANAGED_ITEM, + SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED } from "../../actions/sponsor-forms-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { getSafePageAfterRemove } from "../../utils/methods"; @@ -119,6 +120,20 @@ const sponsorCustomizedFormItemsListReducer = ( }; return { ...state, currentItem }; } + case SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED: { + const { fileId } = payload; + const currentItem = { + ...state.currentItem, + images: + state.currentItem.images?.filter((img) => img.id !== fileId) ?? [] + }; + const items = state.items.map((item) => + item.id === currentItem.id + ? { ...item, images: item.images?.filter((img) => img.id !== fileId) } + : item + ); + return { ...state, currentItem, items }; + } case SPONSOR_CUSTOMIZED_FORM_ITEM_DELETED: { const { itemId } = payload; const items = state.items.filter((it) => it.id !== itemId); diff --git a/src/reducers/sponsors/sponsor-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-form-items-list-reducer.js index 1bb26338d..28b2ccea3 100644 --- a/src/reducers/sponsors/sponsor-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-form-items-list-reducer.js @@ -118,14 +118,19 @@ const sponsorFormItemsListReducer = (state = DEFAULT_STATE, action) => { return { ...state, items }; } case SPONSOR_FORM_ITEM_FILE_DELETED: { - const { fileId } = payload; - const currentItem = { - ...state.currentItem, - images: state.currentItem.images.filter((img) => img.id !== fileId) - }; + const { fileId, itemId } = payload; + const currentItem = + state.currentItem.id === itemId + ? { + ...state.currentItem, + images: state.currentItem.images.filter( + (img) => img.id !== fileId + ) + } + : state.currentItem; const items = state.items.map((item) => - item.id === currentItem.id + item.id === itemId ? { ...item, images: item.images?.filter((img) => img.id !== fileId) } : item ); From 58de2b155dd539e41d6632d8bad305b7797ee92d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 4 Aug 2026 01:05:29 -0300 Subject: [PATCH 09/12] fix: reuse deleteItem code, adjust reducer, fix bug with double image save MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../__tests__/sponsor-forms-actions.test.js | 110 ++++++++++++++- src/actions/inventory-shared-actions.js | 12 +- src/actions/sponsor-forms-actions.js | 129 ++++-------------- ...customized-form-items-list-reducer.test.js | 31 ++++- ...nsor-customized-form-items-list-reducer.js | 18 ++- 5 files changed, 181 insertions(+), 119 deletions(-) diff --git a/src/actions/__tests__/sponsor-forms-actions.test.js b/src/actions/__tests__/sponsor-forms-actions.test.js index 05ac03b63..943db3ea0 100644 --- a/src/actions/__tests__/sponsor-forms-actions.test.js +++ b/src/actions/__tests__/sponsor-forms-actions.test.js @@ -6,6 +6,7 @@ import thunk from "redux-thunk"; import flushPromises from "flush-promises"; import { getRequest, + postRequest, putRequest, deleteRequest } from "openstack-uicore-foundation/lib/utils/actions"; @@ -15,7 +16,9 @@ import { normalizeSponsorCustomizedForm, updateFormTemplateTiers, removeItemFile, - removeSponsorCustomizedFormItemImages + removeSponsorCustomizedFormItemImages, + saveSponsorFormItem, + updateSponsorFormItem } from "../sponsor-forms-actions"; import * as methods from "../../utils/methods"; @@ -365,7 +368,7 @@ describe("Sponsor Forms Actions", () => { jest.restoreAllMocks(); }); - it("dispatches SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED with fileId", async () => { + it("dispatches SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED with fileId and itemId", async () => { const store = mockStore({ currentSummitState: { currentSummit: { id: 42 } }, currentSponsorState: { entity: { id: 5 } } @@ -378,7 +381,7 @@ describe("Sponsor Forms Actions", () => { null, { type: "SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED", - payload: { fileId: 555 } + payload: { fileId: 555, itemId: 99 } }, `${window.PURCHASES_API_URL}/api/v1/summits/42/sponsors/5/sponsor-forms/7/items/99/images/555`, null, @@ -388,7 +391,106 @@ describe("Sponsor Forms Actions", () => { const dispatched = store .getActions() .find((a) => a.type === "SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED"); - expect(dispatched.payload).toEqual({ fileId: 555 }); + expect(dispatched.payload).toEqual({ fileId: 555, itemId: 99 }); + }); + }); + + describe("saveSponsorFormItem", () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + beforeEach(() => { + jest.spyOn(methods, "getAccessTokenSafely").mockReturnValue("TOKEN"); + + postRequest.mockImplementation( + () => () => () => Promise.resolve({ response: { id: 100 } }) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("sends the images in the create request body and makes no follow-up image request", async () => { + const store = mockStore({ + currentSummitState: { currentSummit: { id: 42 } } + }); + + const entity = { + name: "Item", + images: [{ file_path: "data:image/png;base64,AAA" }], + meta_fields: [] + }; + + await store.dispatch(saveSponsorFormItem(7, entity)); + await flushPromises(); + + expect(postRequest).toHaveBeenCalledWith( + null, + expect.any(Function), + `${window.PURCHASES_API_URL}/api/v1/summits/42/show-forms/7/items`, + expect.objectContaining({ + images: [{ file_path: "data:image/png;base64,AAA" }] + }), + expect.any(Function) + ); + + // The item-create request itself now saves and associates the + // images — a follow-up per-image request would create duplicates. + const hitImagesEndpoint = postRequest.mock.calls.some(([, , url]) => + url.includes("/images") + ); + expect(hitImagesEndpoint).toBe(false); + }); + }); + + describe("updateSponsorFormItem", () => { + const middlewares = [thunk]; + const mockStore = configureStore(middlewares); + + beforeEach(() => { + jest.spyOn(methods, "getAccessTokenSafely").mockReturnValue("TOKEN"); + + putRequest.mockImplementation( + () => () => () => Promise.resolve({ response: { id: 100 } }) + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("sends the images in the update request body and makes no follow-up image request", async () => { + const store = mockStore({ + currentSummitState: { currentSummit: { id: 42 } } + }); + + const entity = { + id: 100, + name: "Item", + images: [{ id: 5, file_path: "https://cdn/a.png" }], + meta_fields: [] + }; + + await store.dispatch(updateSponsorFormItem(7, entity)); + await flushPromises(); + + expect(putRequest).toHaveBeenCalledWith( + null, + expect.any(Function), + `${window.PURCHASES_API_URL}/api/v1/summits/42/show-forms/7/items/100`, + expect.objectContaining({ + images: [{ id: 5, file_path: "https://cdn/a.png" }] + }), + expect.any(Function) + ); + + // The item-update request itself now saves and associates the + // images — a follow-up per-image request would create duplicates. + const hitImagesEndpoint = putRequest.mock.calls.some(([, , url]) => + url.includes("/images") + ); + expect(hitImagesEndpoint).toBe(false); }); }); }); diff --git a/src/actions/inventory-shared-actions.js b/src/actions/inventory-shared-actions.js index 64de746a7..fa275dc0d 100644 --- a/src/actions/inventory-shared-actions.js +++ b/src/actions/inventory-shared-actions.js @@ -272,13 +272,15 @@ export const deleteFile = return deleteRequest( null, - createAction(settings.deletedActionName)({ fileId }), + createAction(settings.deletedActionName)({ fileId, ...settings.payload }), `${settings.url}/${fileId}`, null, - authErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + settings.errorHandler ?? authErrorHandler + )(params)(dispatch) + .catch(() => {}) + .finally(() => { + dispatch(stopLoading()); + }); }; /* ************************************ ARCHIVE ************************************ */ diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js index 955bb27f2..1d573e501 100644 --- a/src/actions/sponsor-forms-actions.js +++ b/src/actions/sponsor-forms-actions.js @@ -31,6 +31,7 @@ import { getAccessTokenSafely, normalizeSelectAllField } from "../utils/methods"; +import { deleteFile } from "./inventory-shared-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, @@ -106,8 +107,6 @@ export const RECEIVE_SPONSOR_FORM_ITEM = "RECEIVE_SPONSOR_FORM_ITEM"; export const SPONSOR_FORM_ITEM_UPDATED = "SPONSOR_FORM_ITEM_UPDATED"; export const RESET_SPONSOR_FORM_ITEM = "RESET_SPONSOR_FORM_ITEM"; export const SPONSOR_FORM_ITEM_DELETED = "SPONSOR_FORM_ITEM_DELETED"; -export const SPONSOR_FORM_ITEM_IMAGES_UPDATED = - "SPONSOR_FORM_ITEM_IMAGES_UPDATED"; export const SPONSOR_FORM_ITEM_FILE_DELETED = "SPONSOR_FORM_ITEM_FILE_DELETED"; export const SPONSOR_FORM_ITEMS_ADDED = "SPONSOR_FORM_ITEMS_ADDED"; export const SPONSOR_FORM_ITEM_ARCHIVED = "SPONSOR_FORM_ITEM_ARCHIVED"; @@ -1234,58 +1233,19 @@ export const deleteSponsorFormItem = }); }; -const saveItemImages = - (formId, formItemId, images) => async (dispatch, getState) => { - const { currentSummitState } = getState(); - const { currentSummit } = currentSummitState; - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - - const promises = images.map((file) => { - if (file.id) { - return putRequest( - null, - createAction(SPONSOR_FORM_ITEM_IMAGES_UPDATED), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images/${file.id}`, - file, - authErrorHandler, - file - )(params)(dispatch); - } - return postRequest( - null, - createAction(SPONSOR_FORM_ITEM_IMAGES_UPDATED), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images`, - file, - authErrorHandler, - file - )(params)(dispatch); - }); - - return Promise.all(promises); - }; - export const removeItemFile = (formId, formItemId, fileId) => async (dispatch, getState) => { const { currentSummitState } = getState(); const { currentSummit } = currentSummitState; - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - dispatch(startLoading()); + const settings = { + url: `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images`, + deletedActionName: SPONSOR_FORM_ITEM_FILE_DELETED, + payload: { itemId: formItemId }, + errorHandler: snackbarErrorHandler + }; - return deleteRequest( - null, - createAction(SPONSOR_FORM_ITEM_FILE_DELETED)({ - fileId, - itemId: formItemId - }), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${formItemId}/images/${fileId}`, - null, - snackbarErrorHandler - )(params)(dispatch).finally(() => { - dispatch(stopLoading()); - }); + return deleteFile(fileId, settings)(dispatch); }; export const saveSponsorFormItem = @@ -1309,27 +1269,13 @@ export const saveSponsorFormItem = normalizedEntity, snackbarErrorHandler )(params)(dispatch) - .then(({ response }) => { - const promises = [Promise.resolve(0)]; - - if (normalizedEntity.images?.length > 0) { - const savingImages = saveItemImages( - formId, - response.id, - normalizedEntity.images - )(dispatch, getState); - - promises.push(savingImages); - } - - return Promise.all(promises).then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("sponsor_form_item_list.edit_item.created") - }) - ); - }); + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_form_item_list.edit_item.created") + }) + ); }) .finally(() => { dispatch(stopLoading()); @@ -1358,26 +1304,12 @@ export const updateSponsorFormItem = snackbarErrorHandler )(params)(dispatch) .then(() => { - const promises = [Promise.resolve(0)]; - - if (normalizedEntity.images?.length > 0) { - const savingImages = saveItemImages( - formId, - entity.id, - normalizedEntity.images - )(dispatch, getState); - - promises.push(savingImages); - } - - return Promise.all(promises).then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("sponsor_form_item_list.edit_item.updated") - }) - ); - }); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_form_item_list.edit_item.updated") + }) + ); }) .catch((err) => { throw err; @@ -1786,18 +1718,13 @@ export const removeSponsorCustomizedFormItemImages = const { entity: { id: sponsorId } } = currentSponsorState; - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - dispatch(startLoading()); + const settings = { + url: `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${formItemId}/images`, + deletedActionName: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, + payload: { itemId: formItemId }, + errorHandler: snackbarErrorHandler + }; - return deleteRequest( - null, - createAction(SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED)({ fileId }), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}/items/${formItemId}/images/${fileId}`, - null, - snackbarErrorHandler - )(params)(dispatch).finally(() => { - dispatch(stopLoading()); - }); + return deleteFile(fileId, settings)(dispatch); }; diff --git a/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js index 4187aad1c..062b3ec20 100644 --- a/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/sponsor-customized-form-items-list-reducer.test.js @@ -110,7 +110,7 @@ describe("sponsorCustomizedFormItemsListReducer", () => { const result = sponsorCustomizedFormItemsListReducer(state, { type: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, - payload: { fileId: 10 } + payload: { fileId: 10, itemId: 1 } }); expect(result.currentItem.images).toEqual([{ id: 11 }]); @@ -127,11 +127,38 @@ describe("sponsorCustomizedFormItemsListReducer", () => { const result = sponsorCustomizedFormItemsListReducer(state, { type: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, - payload: { fileId: 10 } + payload: { fileId: 10, itemId: 1 } }); expect(result.currentItem.images).toEqual([]); }); + + it("leaves currentItem untouched when the deleted file belongs to a different item", () => { + // Regression for the race where the delete for item A resolves after + // the dialog switched to item B (RECEIVE_SPONSOR_CUSTOMIZED_FORM_ITEM + // replaced currentItem in between) — only A's row should update. + const state = { + ...DEFAULT_STATE, + currentItem: { + ...DEFAULT_STATE.currentItem, + id: 2, + images: [{ id: 12 }] + }, + items: [ + buildItem({ id: 1, images: [{ id: 10 }, { id: 11 }] }), + buildItem({ id: 2, images: [{ id: 12 }] }) + ] + }; + + const result = sponsorCustomizedFormItemsListReducer(state, { + type: SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED, + payload: { fileId: 10, itemId: 1 } + }); + + expect(result.currentItem).toEqual(state.currentItem); + expect(result.items[0].images).toEqual([{ id: 11 }]); + expect(result.items[1].images).toEqual([{ id: 12 }]); + }); }); describe("SPONSOR_FORM_MANAGED_ITEM_UPDATED", () => { diff --git a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js index e835ff6c8..8f2ec2b94 100644 --- a/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-customized-form-items-list-reducer.js @@ -121,14 +121,18 @@ const sponsorCustomizedFormItemsListReducer = ( return { ...state, currentItem }; } case SPONSOR_CUSTOMIZED_FORM_ITEM_IMAGE_DELETED: { - const { fileId } = payload; - const currentItem = { - ...state.currentItem, - images: - state.currentItem.images?.filter((img) => img.id !== fileId) ?? [] - }; + const { fileId, itemId } = payload; + const currentItem = + state.currentItem.id === itemId + ? { + ...state.currentItem, + images: + state.currentItem.images?.filter((img) => img.id !== fileId) ?? + [] + } + : state.currentItem; const items = state.items.map((item) => - item.id === currentItem.id + item.id === itemId ? { ...item, images: item.images?.filter((img) => img.id !== fileId) } : item ); From 67fdf4e05fda12b582d8a448b3e2f1e79f5c32ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 4 Aug 2026 10:37:23 -0300 Subject: [PATCH 10/12] fix: add catch for no images on reducer delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/reducers/sponsors/sponsor-form-items-list-reducer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/reducers/sponsors/sponsor-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-form-items-list-reducer.js index 28b2ccea3..53b098ebd 100644 --- a/src/reducers/sponsors/sponsor-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-form-items-list-reducer.js @@ -123,9 +123,9 @@ const sponsorFormItemsListReducer = (state = DEFAULT_STATE, action) => { state.currentItem.id === itemId ? { ...state.currentItem, - images: state.currentItem.images.filter( - (img) => img.id !== fileId - ) + images: + state.currentItem.images?.filter((img) => img.id !== fileId) ?? + [] } : state.currentItem; From e7bcca9521659123dfa3ab29ab401f8d2caa3185 Mon Sep 17 00:00:00 2001 From: smarcet Date: Thu, 6 Aug 2026 16:56:02 -0300 Subject: [PATCH 11/12] test: add failing coverage for RECEIVE_SPONSOR_FORM_ITEM dropping file_path on images Mirrors the customized-item reducer's existing file_url -> file_path mapping. Currently red: RECEIVE_SPONSOR_FORM_ITEM stores fetched images as-is, so normalizeItem strips them from the update body and ShowFormItemService.update() deletes all images on save. --- .../sponsor-form-items-list-reducer.test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js b/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js index 2a6dfefc7..19e46c155 100644 --- a/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js +++ b/src/reducers/sponsors/__tests__/sponsor-form-items-list-reducer.test.js @@ -190,6 +190,42 @@ describe("SponsorFormItemsListReducer", () => { } }); }); + + it("maps file_url to file_path on each image - mirrors the customized-item reducer's edit-form image fix", () => { + const item = { + id: "A", + code: "A", + name: "A", + early_bird_rate: 100, + standard_rate: 100, + onsite_rate: 100, + default_quantity: "100", + is_archived: true, + images: [ + { id: 10, file_url: "https://cdn/a.png" }, + { id: 11, file_url: "https://cdn/b.png" } + ], + meta_fields: [] + }; + + result = SponsorFormItemsListReducer(initialState, { + type: RECEIVE_SPONSOR_FORM_ITEM, + payload: { response: item } + }); + + expect(result.currentItem.images).toEqual([ + { + id: 10, + file_url: "https://cdn/a.png", + file_path: "https://cdn/a.png" + }, + { + id: 11, + file_url: "https://cdn/b.png", + file_path: "https://cdn/b.png" + } + ]); + }); }); describe("RESET_SPONSOR_FORM_ITEM", () => { From 32d548e23b52459970fd507ca5d01da213961f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 7 Aug 2026 02:27:29 -0300 Subject: [PATCH 12/12] fix: change reducer to fix mapping images, add guard on delete images from item without id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../sponsors-global/inventory/inventory-list-page.js | 1 + src/pages/sponsors/sponsor-form-item-list-page/index.js | 6 ++++-- .../components/manage-items/sponsor-forms-manage-items.js | 1 + src/reducers/sponsors/sponsor-form-items-list-reducer.js | 8 +++++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/pages/sponsors-global/inventory/inventory-list-page.js b/src/pages/sponsors-global/inventory/inventory-list-page.js index 662b55024..81fa856c2 100644 --- a/src/pages/sponsors-global/inventory/inventory-list-page.js +++ b/src/pages/sponsors-global/inventory/inventory-list-page.js @@ -150,6 +150,7 @@ const InventoryListPage = ({ : archiveInventoryItem(item); const handleRemoveImage = (imageId) => { + if (!currentInventoryItem?.id) return; deleteInventoryItemImage(currentInventoryItem.id, imageId); }; diff --git a/src/pages/sponsors/sponsor-form-item-list-page/index.js b/src/pages/sponsors/sponsor-form-item-list-page/index.js index 39cce493b..4eb853833 100644 --- a/src/pages/sponsors/sponsor-form-item-list-page/index.js +++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js @@ -131,8 +131,10 @@ const SponsorFormItemListPage = ({ ); }; - const handleRemoveItemImage = (imageId) => - removeItemFile(formId, currentItem?.id, imageId); + const handleRemoveItemImage = (imageId) => { + if (!currentItem?.id) return; + removeItemFile(formId, currentItem.id, imageId); + }; const handleAddFromInventory = (itemIds) => addInventoryItems(formId, itemIds).then(() => diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js index 0d6fd64fe..828eab25e 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items.js @@ -203,6 +203,7 @@ const SponsorFormsManageItems = ({ }; const handleImageRemove = (imageId) => { + if (!currentInventoryItem?.id) return; removeSponsorCustomizedFormItemImages( formId, currentInventoryItem.id, diff --git a/src/reducers/sponsors/sponsor-form-items-list-reducer.js b/src/reducers/sponsors/sponsor-form-items-list-reducer.js index 53b098ebd..29869eb68 100644 --- a/src/reducers/sponsors/sponsor-form-items-list-reducer.js +++ b/src/reducers/sponsors/sponsor-form-items-list-reducer.js @@ -100,12 +100,14 @@ const sponsorFormItemsListReducer = (state = DEFAULT_STATE, action) => { } case RECEIVE_SPONSOR_FORM_ITEM: { const item = payload.response; - const currentItem = { ...item, - meta_fields: item.meta_fields.length > 0 ? item.meta_fields : [] + images: (item.images || []).map((img) => ({ + ...img, + file_path: img.file_url + })), + meta_fields: (item.meta_fields ?? []).length > 0 ? item.meta_fields : [] }; - return { ...state, currentItem }; } case RESET_SPONSOR_FORM_ITEM: {