diff --git a/src/actions/sponsor-forms-actions.js b/src/actions/sponsor-forms-actions.js
index 5492628e7..57d8b768c 100644
--- a/src/actions/sponsor-forms-actions.js
+++ b/src/actions/sponsor-forms-actions.js
@@ -1231,37 +1231,6 @@ 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 saveSponsorFormItem =
(formId, entity) => async (dispatch, getState) => {
const { currentSummitState } = getState();
@@ -1276,85 +1245,44 @@ export const saveSponsorFormItem =
const normalizedEntity = normalizeItem(entity);
- return postRequest(
- null,
- createAction(SPONSOR_FORM_ITEM_UPDATED),
- `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items`,
- 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(() => {
+ if (entity.id) {
+ return putRequest(
+ null,
+ createAction(SPONSOR_FORM_ITEM_UPDATED),
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${entity.id}`,
+ normalizedEntity,
+ snackbarErrorHandler
+ )(params)(dispatch)
+ .then(() => {
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
- html: T.translate("sponsor_form_item_list.edit_item.created")
+ html: T.translate("sponsor_form_item_list.edit_item.updated")
})
);
+ })
+ .catch((err) => {
+ throw err;
+ })
+ .finally(() => {
+ dispatch(stopLoading());
});
- })
- .finally(() => {
- dispatch(stopLoading());
- });
- };
-
-export const updateSponsorFormItem =
- (formId, entity) => async (dispatch, getState) => {
- const { currentSummitState } = getState();
- const accessToken = await getAccessTokenSafely();
- const { currentSummit } = currentSummitState;
-
- dispatch(startLoading());
-
- const params = {
- access_token: accessToken
- };
-
- const normalizedEntity = normalizeItem(entity);
+ }
- return putRequest(
+ return postRequest(
null,
createAction(SPONSOR_FORM_ITEM_UPDATED),
- `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items/${entity.id}`,
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms/${formId}/items`,
normalizedEntity,
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")
- })
- );
- });
- })
- .catch((err) => {
- throw err;
+ dispatch(
+ snackbarSuccessHandler({
+ title: T.translate("general.success"),
+ html: T.translate("sponsor_form_item_list.edit_item.created")
+ })
+ );
})
.finally(() => {
dispatch(stopLoading());
diff --git a/src/pages/sponsors-global/form-templates/__tests__/sponsor-inventory-popup.test.js b/src/pages/sponsors-global/form-templates/__tests__/sponsor-inventory-popup.test.js
new file mode 100644
index 000000000..d9b04b287
--- /dev/null
+++ b/src/pages/sponsors-global/form-templates/__tests__/sponsor-inventory-popup.test.js
@@ -0,0 +1,187 @@
+import React from "react";
+import { render, screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom";
+import SponsorItemDialog from "../sponsor-inventory-popup";
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ translate: jest.fn((key) => key)
+}));
+
+jest.mock("../../../../hooks/useScrollToError", () => jest.fn());
+
+jest.mock("openstack-uicore-foundation/lib/components", () => ({
+ MuiFormikUpload: function MockMuiFormikUpload({ name }) {
+ return
;
+ }
+}));
+
+jest.mock(
+ "openstack-uicore-foundation/lib/components/mui/formik-inputs/additional-input-list",
+ () =>
+ function MockAdditionalInputList({ name }) {
+ return ;
+ }
+);
+
+jest.mock(
+ "../../../../components/mui/formik-inputs/item-price-tiers",
+ () =>
+ function MockItemPriceTiers() {
+ return ;
+ }
+);
+
+jest.mock(
+ "../../../../components/inputs/formik-text-editor",
+ () =>
+ function MockFormikTextEditor({ name }) {
+ return ;
+ }
+);
+
+const BASE_ENTITY = {
+ id: 0,
+ code: "",
+ name: "",
+ description: "",
+ early_bird_rate: "",
+ standard_rate: "",
+ onsite_rate: "",
+ quantity_limit_per_show: "",
+ quantity_limit_per_sponsor: "",
+ meta_fields: [],
+ images: []
+};
+
+const fillRequiredTextFields = async (user) => {
+ await user.type(document.querySelector("input[name=\"code\"]"), "CODE-1");
+ await user.type(document.querySelector("input[name=\"name\"]"), "Item 1");
+};
+
+const submit = async (user) => {
+ await user.click(
+ screen.getByRole("button", { name: "edit_inventory_item.save_changes" })
+ );
+};
+
+describe("SponsorItemDialog", () => {
+ let onSave;
+ let onClose;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ onSave = jest.fn(() => Promise.resolve());
+ onClose = jest.fn();
+ });
+
+ it("titles itself by whether the entity has an id", () => {
+ const { rerender } = render(
+
+ );
+ expect(
+ screen.getByText("edit_inventory_item.new_item")
+ ).toBeInTheDocument();
+
+ rerender(
+
+ );
+ expect(
+ screen.getByText("edit_inventory_item.edit_item")
+ ).toBeInTheDocument();
+ });
+
+ it("blocks save when code/name are empty", async () => {
+ const user = userEvent.setup();
+ render(
+
+ );
+
+ await submit(user);
+
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ describe("default_quantity requirement", () => {
+ it("is optional by default: saves with no value and shows no required marker", async () => {
+ const user = userEvent.setup();
+ render(
+
+ );
+
+ expect(
+ screen.queryByText("edit_inventory_item.default_quantity *")
+ ).not.toBeInTheDocument();
+
+ await fillRequiredTextFields(user);
+ await submit(user);
+
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
+ expect(onClose).toHaveBeenCalledTimes(1);
+ });
+
+ it("blocks save, shows the error and the required marker when required and empty", async () => {
+ const user = userEvent.setup();
+ render(
+
+ );
+
+ expect(
+ screen.getByText("edit_inventory_item.default_quantity *")
+ ).toBeInTheDocument();
+
+ await fillRequiredTextFields(user);
+ await submit(user);
+
+ expect(onSave).not.toHaveBeenCalled();
+ expect(
+ await screen.findByText("validation.required")
+ ).toBeInTheDocument();
+ });
+
+ it("allows save once a value is provided when required", async () => {
+ const user = userEvent.setup();
+ render(
+
+ );
+
+ await fillRequiredTextFields(user);
+ await user.type(
+ document.querySelector("input[name=\"default_quantity\"]"),
+ "5"
+ );
+ await submit(user);
+
+ await waitFor(() => expect(onSave).toHaveBeenCalledTimes(1));
+ expect(onSave.mock.calls[0][0]).toEqual(
+ expect.objectContaining({ default_quantity: 5 })
+ );
+ });
+ });
+});
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..8d4e75a3e 100644
--- a/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js
+++ b/src/pages/sponsors-global/form-templates/sponsor-inventory-popup.js
@@ -43,7 +43,8 @@ const SponsorItemDialog = ({
onSave,
onMetaFieldTypeDeleted,
onMetaFieldTypeValueDeleted,
- entity: initialEntity
+ entity: initialEntity,
+ requireDefaultQuantity = false
}) => {
const [isSaving, setIsSaving] = useState(false);
@@ -62,7 +63,11 @@ const SponsorItemDialog = ({
early_bird_rate: nullableDecimalValidation(),
standard_rate: nullableDecimalValidation(),
onsite_rate: nullableDecimalValidation(),
- default_quantity: positiveNumberValidation(),
+ default_quantity: requireDefaultQuantity
+ ? positiveNumberValidation().required(
+ T.translate("validation.required")
+ )
+ : positiveNumberValidation(),
quantity_limit_per_sponsor: positiveNumberValidation(),
quantity_limit_per_show: positiveNumberValidation(),
meta_fields: formMetafieldsValidation()
@@ -173,6 +178,7 @@ const SponsorItemDialog = ({
{T.translate("edit_inventory_item.default_quantity")}
+ {requireDefaultQuantity && " *"}
({
...jest.requireActual("../../../../actions/sponsor-forms-actions"),
getSponsorFormItems: jest.fn(() => () => Promise.resolve()),
- updateSponsorFormItem: jest.fn(() => () => Promise.resolve()),
+ saveSponsorFormItem: jest.fn(() => () => Promise.resolve()),
addInventoryItems: jest.fn(() => () => Promise.resolve())
}));
@@ -28,7 +28,7 @@ jest.mock(
const {
getSponsorFormItems,
- updateSponsorFormItem,
+ saveSponsorFormItem,
addInventoryItems
} = require("../../../../actions/sponsor-forms-actions");
@@ -82,7 +82,7 @@ describe("SponsorFormItemListPage inline cell edit", () => {
fireEvent.change(input, { target: { value: "200" } });
fireEvent.blur(input);
- expect(updateSponsorFormItem).toHaveBeenCalledWith(
+ expect(saveSponsorFormItem).toHaveBeenCalledWith(
"FORM1",
expect.objectContaining({ id: 1 })
);
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
deleted file mode 100644
index 26abd82d4..000000000
--- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-form.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import React from "react";
-import {
- Box,
- Button,
- DialogActions,
- DialogContent,
- Divider,
- FormLabel,
- Grid2,
- InputLabel,
- Typography
-} from "@mui/material";
-import T from "i18n-react";
-import * as yup from "yup";
-import { FormikProvider, useFormik } from "formik";
-import { MuiFormikUpload } from "openstack-uicore-foundation/lib/components";
-import AdditionalInputList from "openstack-uicore-foundation/lib/components/mui/formik-inputs/additional-input-list";
-import {
- addIssAfterDateFieldValidator,
- nullableDecimalValidation,
- formMetafieldsValidation,
- positiveNumberValidation,
- requiredStringValidation
-} from "../../../../utils/yup";
-import MuiFormikTextField from "../../../../components/mui/formik-inputs/mui-formik-textfield";
-import useScrollToError from "../../../../hooks/useScrollToError";
-import ItemPriceTiers from "../../../../components/mui/formik-inputs/item-price-tiers";
-import FormikTextEditor from "../../../../components/inputs/formik-text-editor";
-import MuiFormikQuantityField from "../../../../components/mui/formik-inputs/mui-formik-quantity-field";
-import { MAX_INVENTORY_IMAGES_UPLOAD_QTY } from "../../../../utils/constants";
-import { getFileUploadAllowedExtensions } from "../../../../utils/methods";
-
-const buildInitialValues = (data) => ({ ...data });
-
-addIssAfterDateFieldValidator();
-
-const SponsorFormItemForm = ({ initialValues, onSubmit, isSaving }) => {
- const formik = useFormik({
- initialValues: buildInitialValues(initialValues),
- validationSchema: yup.object({
- code: requiredStringValidation(),
- name: requiredStringValidation(),
- early_bird_rate: nullableDecimalValidation(),
- standard_rate: nullableDecimalValidation(),
- onsite_rate: nullableDecimalValidation(),
- default_quantity: positiveNumberValidation().required(
- T.translate("validation.required")
- ),
- quantity_limit_per_sponsor: positiveNumberValidation(),
- quantity_limit_per_show: positiveNumberValidation(),
- meta_fields: formMetafieldsValidation()
- }),
- onSubmit,
- enableReinitialize: true
- });
-
- // SCROLL TO ERROR
- useScrollToError(formik);
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {T.translate("sponsor_form_item_list.edit_item.description")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {T.translate(
- "sponsor_form_item_list.edit_item.additional_fields"
- )}
-
-
-
-
-
-
-
- {T.translate("sponsor_form_item_list.edit_item.images")}
-
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default SponsorFormItemForm;
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
deleted file mode 100644
index b908a5c4d..000000000
--- a/src/pages/sponsors/sponsor-form-item-list-page/components/sponsor-form-item-popup.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React, { useState } from "react";
-import PropTypes from "prop-types";
-import T from "i18n-react/dist/i18n-react";
-import {
- Dialog,
- DialogTitle,
- Divider,
- IconButton,
- Typography
-} from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import SponsorFormItemForm from "./sponsor-form-item-form";
-
-const SponsorFormItemPopup = ({ item, onClose, onSave }) => {
- const [isSaving, setIsSaving] = useState(false);
-
- const handleClose = () => {
- if (isSaving) return;
- onClose();
- };
-
- const handleOnSave = (values) => {
- if (isSaving) return;
- setIsSaving(true);
- onSave(values)
- .then(() => onClose())
- .catch(() => {})
- .finally(() => setIsSaving(false));
- };
-
- return (
-
- );
-};
-
-SponsorFormItemPopup.propTypes = {
- onClose: PropTypes.func.isRequired,
- onSave: PropTypes.func.isRequired,
- item: PropTypes.object
-};
-
-export default SponsorFormItemPopup;
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..0e2f2f8fb 100644
--- a/src/pages/sponsors/sponsor-form-item-list-page/index.js
+++ b/src/pages/sponsors/sponsor-form-item-list-page/index.js
@@ -32,14 +32,13 @@ import {
getSponsorFormItem,
getSponsorFormItems,
saveSponsorFormItem,
- updateSponsorFormItem,
addInventoryItems,
resetSponsorFormItem,
archiveSponsorFormItem,
unarchiveSponsorFormItem
} from "../../../actions/sponsor-forms-actions";
import { getInventoryItems } from "../../../actions/inventory-item-actions";
-import SponsorFormItemPopup from "./components/sponsor-form-item-popup";
+import SponsorInventoryDialog from "../../sponsors-global/form-templates/sponsor-inventory-popup";
import SponsorFormAddItemFromInventoryPopup from "./components/sponsor-form-add-item-from-inventory-popup";
import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants";
import { rateCellValidation } from "../../../utils/yup";
@@ -61,7 +60,6 @@ const SponsorFormItemListPage = ({
getSponsorFormItem,
deleteSponsorFormItem,
saveSponsorFormItem,
- updateSponsorFormItem,
addInventoryItems,
resetSponsorFormItem,
archiveSponsorFormItem,
@@ -115,9 +113,8 @@ const SponsorFormItemListPage = ({
setOpenPopup(null);
};
- const handleSaveItem = (values) => {
- const save = values.id ? updateSponsorFormItem : saveSponsorFormItem;
- return save(formId, values).then(() =>
+ const handleSaveItem = (values) =>
+ saveSponsorFormItem(formId, values).then(() =>
getSponsorFormItems(
formId,
values.id ? currentPage : DEFAULT_CURRENT_PAGE,
@@ -127,7 +124,6 @@ const SponsorFormItemListPage = ({
showArchived
).catch(() => {})
);
- };
const handleAddFromInventory = (itemIds) =>
addInventoryItems(formId, itemIds).then(() =>
@@ -145,7 +141,7 @@ const SponsorFormItemListPage = ({
// since editable cell is TextField and not PriceField, we need to convert to cents
const valueInCents = rateToCents(value);
const tmpEntity = { id: rowId, [column]: valueInCents };
- return updateSponsorFormItem(formId, tmpEntity)
+ return saveSponsorFormItem(formId, tmpEntity)
.then(() =>
getSponsorFormItems(
formId,
@@ -355,10 +351,11 @@ const SponsorFormItemListPage = ({
)}
{openPopup === "crud" && (
-
)}
{openPopup === "inventory" && (
@@ -386,7 +383,6 @@ export default connect(mapStateToProps, {
deleteSponsorFormItem,
getSponsorFormItem,
saveSponsorFormItem,
- updateSponsorFormItem,
addInventoryItems,
resetSponsorFormItem,
getInventoryItems,