Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/actions/sponsor-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2279,22 +2279,26 @@ export const querySummitSponsorships = _.debounce(
DEBOUNCE_WAIT
);

export const querySummitAddons = _.debounce(
async (input, summitId, callback) => {
const accessToken = await getAccessTokenSafely();
const endpoint = URI(
`${window.API_BASE_URL}/api/v1/summits/${summitId}/add-ons/metadata`
);
endpoint.addQuery("access_token", accessToken);
fetch(endpoint)
.then(fetchResponseHandler)
.then((data) => {
callback(data);
})
.catch(fetchErrorHandler);
},
DEBOUNCE_WAIT
);
export const querySummitAddons = async (
summitId,
callback
) => {
const accessToken = await getAccessTokenSafely();
const endpoint = URI(
`${window.API_BASE_URL}/api/v1/summits/${summitId}/add-ons/metadata`
);
endpoint.addQuery("access_token", accessToken);
endpoint.addQuery("page", 1);
endpoint.addQuery("per_page", MAX_PER_PAGE);

return fetch(endpoint)
.then(fetchResponseHandler)
.then((data) => callback(data))
.catch((error) => {
fetchErrorHandler(error);
return [];
});
};

export const querySponsorAddons = async (
summitId,
Expand Down
142 changes: 140 additions & 2 deletions src/actions/sponsor-cart-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,30 @@ import {
getRequest,
deleteRequest,
putRequest,
postRequest,
startLoading,
stopLoading
} from "openstack-uicore-foundation/lib/utils/actions";

import { amountToCents } from "openstack-uicore-foundation/lib/utils/money";
import T from "i18n-react";
import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods";
import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions";
import { ERROR_CODE_404 } from "../utils/constants";
import {
DEFAULT_CURRENT_PAGE,
DEFAULT_ORDER_DIR,
DEFAULT_PER_PAGE,
ERROR_CODE_404
} from "../utils/constants";

export const REQUEST_SPONSOR_CART = "REQUEST_SPONSOR_CART";
export const RECEIVE_SPONSOR_CART = "RECEIVE_SPONSOR_CART";
export const SPONSOR_CART_FORM_DELETED = "SPONSOR_CART_FORM_DELETED";
export const SPONSOR_CART_FORM_LOCKED = "SPONSOR_CART_FORM_LOCKED";
export const REQUEST_CART_AVAILABLE_FORMS = "REQUEST_CART_AVAILABLE_FORMS";
export const RECEIVE_CART_AVAILABLE_FORMS = "RECEIVE_CART_AVAILABLE_FORMS";
export const REQUEST_CART_SPONSOR_FORM = "REQUEST_CART_SPONSOR_FORM";
export const RECEIVE_CART_SPONSOR_FORM = "RECEIVE_CART_SPONSOR_FORM";
export const FORM_CART_SAVED = "FORM_CART_SAVED";

const customErrorHandler = (err, res) => (dispatch, state) => {
const code = err.status;
Expand Down Expand Up @@ -163,3 +174,130 @@ export const unlockSponsorCartForm = (formId) => async (dispatch, getState) => {
dispatch(stopLoading());
});
};

export const getSponsorFormsForCart =
(
term = "",
currentPage = DEFAULT_CURRENT_PAGE,
order = "id",
orderDir = DEFAULT_ORDER_DIR
) =>
async (dispatch, getState) => {
const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
const accessToken = await getAccessTokenSafely();
const filter = ["has_items==1"];

dispatch(startLoading());

if (term) {
const escapedTerm = escapeFilterValue(term);
filter.push(`name=@${escapedTerm},code=@${escapedTerm}`);
}

const params = {
page: currentPage,
fields: "id,code,name,items",
per_page: DEFAULT_PER_PAGE,
access_token: accessToken
};

if (filter.length > 0) {
params["filter[]"] = filter;
}

// order
if (order != null && orderDir != null) {
const orderDirSign = orderDir === 1 ? "" : "-";
params.order = `${orderDirSign}${order}`;
}

return getRequest(
createAction(REQUEST_CART_AVAILABLE_FORMS),
createAction(RECEIVE_CART_AVAILABLE_FORMS),
`${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/show-forms`,
authErrorHandler,
{ term, order, orderDir, currentPage }
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
};

// get sponsor show form by id USING V2 API
export const getSponsorForm = (formId) => async (dispatch, getState) => {
const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
const accessToken = await getAccessTokenSafely();

dispatch(startLoading());

const params = {
access_token: accessToken
};

return getRequest(
createAction(REQUEST_CART_SPONSOR_FORM),
createAction(RECEIVE_CART_SPONSOR_FORM),
`${window.PURCHASES_API_URL}/api/v2/summits/${currentSummit.id}/show-forms/${formId}`,
authErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
};

const normalizeItems = (items) =>
items.map((item) => {
const { quantity, custom_rate, ...normalizedItem } = item;
const hasQtyFields = item.meta_fields.some(
(f) => f.class_field === "Form" && f.type_name === "Quantity"
);
const metaFields = item.meta_fields.filter(
(item) => item.current_value !== null
);

return {
...normalizedItem,
...(hasQtyFields ? {} : { quantity }),
...(custom_rate > 0 ? { custom_rate: amountToCents(custom_rate) } : {}),
meta_fields: metaFields
};
});

export const addCartForm =
(formId, addOnId, formValues) => async (dispatch, getState) => {
const { currentSummitState, currentSponsorState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
const { entity: sponsor } = currentSponsorState;

const params = {
access_token: accessToken
};

dispatch(startLoading());

const normalizedEntity = {
form_id: formId,
addon_id: addOnId,
discount_type: formValues.discount_type,
discount_value: formValues.discount_amount,
items: normalizeItems(formValues.items)
Comment on lines +279 to +284
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Normalize amount discounts before sending discount_value.

discount_amount comes from a dollar input, but payload sends it raw. This is inconsistent with the cents-based math used for totals and custom_rate normalization.

💡 Suggested fix
+import { FORM_DISCOUNT_OPTIONS } from "../utils/constants";
@@
     const normalizedEntity = {
       form_id: formId,
       addon_id: addOnId,
       discount_type: formValues.discount_type,
-      discount_value: formValues.discount_amount,
+      discount_value:
+        formValues.discount_type === FORM_DISCOUNT_OPTIONS.AMOUNT
+          ? amountToCents(formValues.discount_amount || 0)
+          : formValues.discount_amount,
       items: normalizeItems(formValues.items)
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-cart-actions.js` around lines 279 - 284, The payload
currently assigns formValues.discount_amount (a dollar value) directly to
normalizedEntity.discount_value, causing inconsistency with cents-based math;
update the code that builds normalizedEntity (the normalizedEntity object in the
sponsor cart action) to convert formValues.discount_amount from dollars to
integer cents (e.g., multiply by 100 and round/parseInt) before assigning to
discount_value so it matches custom_rate/item normalization and totals
calculation; ensure the conversion is done where normalizedEntity is created so
downstream code using discount_value receives cents consistently.

};

return postRequest(
null,
createAction(FORM_CART_SAVED),
`${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/forms`,
normalizedEntity,
snackbarErrorHandler
)(params)(dispatch)
.then(() => {
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("sponsor_list.sponsor_added")
})
);
})
.finally(() => dispatch(stopLoading()));
};
5 changes: 4 additions & 1 deletion src/components/CustomTheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ const theme = createTheme({
MuiFormHelperText: {
styleOverrides: {
root: {
fontSize: ".8em"
fontSize: ".8em",
position: "absolute",
top: "100%",
marginTop: "4px"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import EditIcon from "@mui/icons-material/Edit";

import useScrollToError from "../../../hooks/useScrollToError";
import MuiFormikTextField from "../../mui/formik-inputs/mui-formik-textfield";
import SummitAddonSelect from "../../mui/formik-inputs/summit-addon-select";
import MuiFormikSummitAddonSelect from "../../mui/formik-inputs/mui-formik-summit-addon-select";

const ManageTierAddonsPopup = ({
sponsorship,
Expand Down Expand Up @@ -223,14 +223,16 @@ const ManageTierAddonsPopup = ({
</InputLabel>
{editingRow === index ? (
<Box width="100%">
<SummitAddonSelect
<MuiFormikSummitAddonSelect
name={`addons[${index}].type`}
fullWidth
placeholder={T.translate(
"edit_sponsor.placeholders.select"
)}
summitId={summitId}
margin="none"
inputProps={{
fullWidth: true,
margin: "none"
}}
/>
</Box>
) : (
Expand Down Expand Up @@ -318,15 +320,16 @@ const ManageTierAddonsPopup = ({
{T.translate("edit_sponsor.addon_type")}
</InputLabel>
<Box width="100%">
<SummitAddonSelect
<MuiFormikSummitAddonSelect
name="newAddon.type"
formik={formik}
fullWidth
placeholder={T.translate(
"edit_sponsor.placeholders.select"
)}
summitId={summitId}
margin="none"
inputProps={{
fullWidth: true,
margin: "none"
}}
/>
</Box>
</Grid2>
Expand Down
Loading
Loading