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
149 changes: 144 additions & 5 deletions src/actions/sponsor-pages-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,24 @@ import {
createAction,
getRequest,
postRequest,
putRequest,
startLoading,
stopLoading,
authErrorHandler,
escapeFilterValue
} from "openstack-uicore-foundation/lib/utils/actions";
import T from "i18n-react/dist/i18n-react";
import moment from "moment-timezone";
import { getAccessTokenSafely } from "../utils/methods";
import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions";
import {
DEFAULT_CURRENT_PAGE,
DEFAULT_ORDER_DIR,
DEFAULT_PER_PAGE
DEFAULT_PER_PAGE,
PAGES_MODULE_KINDS
} from "../utils/constants";

export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED";
export const RESET_EDIT_PAGE = "RESET_EDIT_PAGE";

export const REQUEST_SPONSOR_MANAGED_PAGES = "REQUEST_SPONSOR_MANAGED_PAGES";
export const RECEIVE_SPONSOR_MANAGED_PAGES = "RECEIVE_SPONSOR_MANAGED_PAGES";
Expand All @@ -39,6 +42,11 @@ export const REQUEST_SPONSOR_CUSTOMIZED_PAGES =
"REQUEST_SPONSOR_CUSTOMIZED_PAGES";
export const RECEIVE_SPONSOR_CUSTOMIZED_PAGES =
"RECEIVE_SPONSOR_CUSTOMIZED_PAGES";
export const RECEIVE_SPONSOR_CUSTOMIZED_PAGE =
"RECEIVE_SPONSOR_CUSTOMIZED_PAGE";
export const SPONSOR_CUSTOMIZED_PAGE_ADDED = "SPONSOR_CUSTOMIZED_PAGE_ADDED";
export const SPONSOR_CUSTOMIZED_PAGE_UPDATED =
"SPONSOR_CUSTOMIZED_PAGE_UPDATED";

export const cloneGlobalPage =
(pagesIds, sponsorIds, allSponsors) => async (dispatch, getState) => {
Expand Down Expand Up @@ -80,6 +88,10 @@ export const cloneGlobalPage =
.finally(() => dispatch(stopLoading()));
};

export const resetSponsorPage = () => (dispatch) => {
dispatch(createAction(RESET_EDIT_PAGE)({}));
};

/* ************************************************************************ */
/* MANAGED PAGES */
/* ************************************************************************ */
Expand Down Expand Up @@ -133,7 +145,7 @@ export const getSponsorManagedPages =
createAction(REQUEST_SPONSOR_MANAGED_PAGES),
createAction(RECEIVE_SPONSOR_MANAGED_PAGES),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/managed-pages`,
authErrorHandler,
snackbarErrorHandler,
{ order, orderDir, page, perPage, term, hideArchived, summitTZ }
)(params)(dispatch).then(() => {
dispatch(stopLoading());
Expand Down Expand Up @@ -215,7 +227,9 @@ export const getSponsorCustomizedPages =

const params = {
page,
fields: "id,code,name,kind,modules_count,allowed_add_ons",
fields:
"id,code,name,allowed_add_ons,is_archived,modules,allowed_add_ons.type,allowed_add_ons.name,allowed_add_ons.id",
expand: "allowed_add_ons",
per_page: perPage,
access_token: accessToken
};
Expand All @@ -236,9 +250,134 @@ export const getSponsorCustomizedPages =
createAction(REQUEST_SPONSOR_CUSTOMIZED_PAGES),
createAction(RECEIVE_SPONSOR_CUSTOMIZED_PAGES),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages`,
authErrorHandler,
snackbarErrorHandler,
{ order, orderDir, page, perPage, term, hideArchived, summitTZ }
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
};

export const getSponsorCustomizedPage =
(pageId) => async (dispatch, getState) => {
const { currentSummitState, currentSponsorState } = getState();
const { currentSummit } = currentSummitState;
const {
entity: { id: sponsorId }
} = currentSponsorState;
const accessToken = await getAccessTokenSafely();

dispatch(startLoading());

const params = {
fields:
"id,code,name,allowed_add_ons,is_archived,modules,allowed_add_ons.name,allowed_add_ons.id",
expand: "allowed_add_ons",
access_token: accessToken
};

return getRequest(
null,
createAction(RECEIVE_SPONSOR_CUSTOMIZED_PAGE),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages/${pageId}`,
snackbarErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
});
};

export const saveSponsorCustomizedPage =
(entity) => async (dispatch, getState) => {
const { currentSummitState, currentSponsorState } = getState();
const { currentSummit } = currentSummitState;
const {
entity: { id: sponsorId }
} = currentSponsorState;
const summitTZ = currentSummit.time_zone.name;
const accessToken = await getAccessTokenSafely();

dispatch(startLoading());

const normalizedEntity = normalizeSponsorCustomPage(entity, summitTZ);

const params = {
access_token: accessToken,
fields: "id,code,name,kind,modules_count,allowed_add_ons"
};

if (entity.id) {
return putRequest(
null,
createAction(SPONSOR_CUSTOMIZED_PAGE_UPDATED),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages/${entity.id}`,
normalizedEntity,
snackbarErrorHandler,
entity
)(params)(dispatch)
.then(() => {
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("edit_sponsor.pages_tab.custom_page_saved")
})
);
})
.finally(() => {
dispatch(stopLoading());
});
}

return postRequest(
null,
createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages`,
normalizedEntity,
snackbarErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("edit_sponsor.pages_tab.custom_page_created")
})
);
});
Comment on lines +329 to +343
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

Missing .finally() on POST request will leave loading spinner on error.

The PUT request (lines 304-322) correctly uses .finally() to call stopLoading(), but the POST request dispatches stopLoading() inside .then(). If the POST request fails, the loading spinner will remain indefinitely.

Proposed fix: Move stopLoading to a .finally() block
     return postRequest(
       null,
       createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED),
       `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages`,
       normalizedEntity,
       snackbarErrorHandler
-    )(params)(dispatch).then(() => {
-      dispatch(stopLoading());
+    )(params)(dispatch)
+      .then(() => {
         dispatch(
           snackbarSuccessHandler({
             title: T.translate("general.success"),
             html: T.translate("edit_sponsor.pages_tab.custom_page_created")
           })
         );
-    });
+      })
+      .finally(() => {
+        dispatch(stopLoading());
+      });
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return postRequest(
null,
createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages`,
normalizedEntity,
snackbarErrorHandler
)(params)(dispatch).then(() => {
dispatch(stopLoading());
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("edit_sponsor.pages_tab.custom_page_created")
})
);
});
return postRequest(
null,
createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED),
`${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-pages`,
normalizedEntity,
snackbarErrorHandler
)(params)(dispatch)
.then(() => {
dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("edit_sponsor.pages_tab.custom_page_created")
})
);
})
.finally(() => {
dispatch(stopLoading());
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/sponsor-pages-actions.js` around lines 325 - 339, The POST branch
calling postRequest with createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED) should
dispatch stopLoading() in a .finally() so the loading spinner is cleared on
errors; update the promise chain that currently calls dispatch(stopLoading())
inside .then() to instead attach a .finally(() => dispatch(stopLoading())) while
keeping the snackbarSuccessHandler in .then() and the snackbarErrorHandler
passed to postRequest unchanged (symbols: postRequest,
createAction(SPONSOR_CUSTOMIZED_PAGE_ADDED), snackbarSuccessHandler,
snackbarErrorHandler, stopLoading).

};

const normalizeSponsorCustomPage = (entity, summitTZ) => {
const normalizedEntity = {
...entity,
apply_to_all_add_ons: false
};

if (entity.allowed_add_ons.includes("all")) {
normalizedEntity.apply_to_all_add_ons = true;
normalizedEntity.allowed_add_ons = [];
} else {
normalizedEntity.allowed_add_ons = entity.allowed_add_ons.map((e) => e.id);
}

normalizedEntity.modules = entity.modules.map((module) => {
const normalizedModule = { ...module };

if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.upload_deadline) {
normalizedModule.upload_deadline = moment
.tz(module.upload_deadline, summitTZ)
.unix();
}

if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.file_type_id) {
normalizedModule.file_type_id =
module.file_type_id?.value || module.file_type_id;
}

if (module.kind === PAGES_MODULE_KINDS.DOCUMENT && module.file) {
normalizedModule.file = module.file[0] || null;
}

delete normalizedModule._tempId;

return normalizedModule;
});

return normalizedEntity;
};
13 changes: 6 additions & 7 deletions src/components/inputs/formik-text-editor.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import React from "react";
import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3";
import { useFormikContext } from "formik";
import { useField } from "formik";
import normalizeHtmlString from "../../utils/normalize-html-string";

const FormikTextEditor = ({ name, ...props }) => {
const { values, errors, touched, setFieldValue, setFieldTouched } =
useFormikContext();
const [field, meta, helpers] = useField(name);

return (
<TextEditorV3
name={name}
id={name}
value={values[name]}
value={field.value}
onChange={(e) => {
const stringValue = normalizeHtmlString(e.target.value);
setFieldValue(name, stringValue);
helpers.setValue(stringValue);
}}
onBlur={() => setFieldTouched(name, true)}
error={touched?.[name] && errors?.[name] ? errors?.[name] : ""}
onBlur={() => helpers.setTouched(true)}
error={meta.touched && meta.error ? meta.error : ""}
license={process.env.JODIT_LICENSE_KEY}
{...props}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/mui/dropdown-checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const DropdownCheckbox = ({
};

return (
<FormControl fullWidth>
<FormControl fullWidth margin="normal">
<InputLabel id={`${name}_label`}>{label}</InputLabel>
<Select
labelId={`${name}_label`}
Expand Down
4 changes: 3 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2485,7 +2485,9 @@
"no_add_ons": "No Add-ons Available",
"no_pages": "There is no pages that match the criteria",
"add_page_using_template": "Add Page Template",
"add_selected_page_template": "Add Selected Page Template"
"add_selected_page_template": "Add Selected Page Template",
"custom_page_saved": "Custom Sponsor Page saved",
"custom_page_created": "Custom Sponsor Page created"
},
"cart_tab": {
"new_form": "New Form",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from "react";
import T from "i18n-react/dist/i18n-react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import {
Box,
Expand All @@ -23,9 +22,14 @@ import MuiFormikTextField from "../../../../components/mui/formik-inputs/mui-for
import PageModules from "./page-template-modules-form";
import {
BYTES_PER_MB,
COLUMN_4,
COLUMN_8,
PAGES_MODULE_KINDS,
PAGE_MODULES_MEDIA_TYPES
} from "../../../../utils/constants";
import DropdownCheckbox from "../../../../components/mui/dropdown-checkbox";
import MuiFormikSelectGroup from "../../../../components/mui/formik-inputs/mui-formik-select-group";
import { querySponsorAddons } from "../../../../actions/sponsor-actions";

const normalizeModules = (modules = [], summitTZ = "UTC") =>
modules.map((m) => {
Expand All @@ -50,11 +54,19 @@ const normalizeModules = (modules = [], summitTZ = "UTC") =>

const PageTemplatePopup = ({
pageTemplate,
open,
onClose,
onSave,
summitTZ
summitTZ,
sponsorships,
summitId,
sponsorId,
sponsorshipIds
}) => {
const showSponsorships =
Array.isArray(sponsorships) && sponsorships.length > 0;

const showAllowedAddons = summitId && sponsorId && sponsorshipIds?.length > 0;

const handleClose = () => {
onClose();
};
Expand Down Expand Up @@ -171,7 +183,7 @@ const PageTemplatePopup = ({
});

return (
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
<Dialog open onClose={handleClose} maxWidth="md" fullWidth>
<DialogTitle sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography fontSize="1.5rem">
{T.translate("page_template_list.page_crud.title")}
Expand All @@ -197,13 +209,46 @@ const PageTemplatePopup = ({
fullWidth
/>
</Grid2>
<Grid2 spacing={2} size={8}>
<Grid2
spacing={2}
size={
showSponsorships || showAllowedAddons ? COLUMN_4 : COLUMN_8
}
>
<MuiFormikTextField
name="name"
label={T.translate("page_template_list.name")}
fullWidth
/>
</Grid2>
{showSponsorships && (
<Grid2 spacing={2} size={4}>
<DropdownCheckbox
name="sponsorship_types"
label={T.translate("page_template_list.sponsorship")}
allLabel={T.translate("page_template_list.all_tiers")}
value={formik.values.sponsorship_types}
options={sponsorships}
onChange={formik.handleChange}
/>
</Grid2>
)}
{showAllowedAddons && (
<Grid2 spacing={2} size={4} sx={{ py: 2 }}>
<MuiFormikSelectGroup
name="allowed_add_ons"
queryFunction={querySponsorAddons}
// params for function, except input
queryParams={[summitId, sponsorId, sponsorshipIds]}
showSelectAll
getGroupId={(addon) => addon.sponsorship.type.id}
getGroupLabel={(addon) => addon.sponsorship.type.type.name}
placeholder={T.translate(
"edit_sponsor.placeholders.select_add_ons"
)}
/>
</Grid2>
)}
</Grid2>
<Divider gutterBottom />
<Grid2 container spacing={2} size={12} sx={{ p: 2 }}>
Expand Down Expand Up @@ -256,14 +301,13 @@ const PageTemplatePopup = ({
};

PageTemplatePopup.propTypes = {
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
summitTZ: PropTypes.string.isRequired
summitTZ: PropTypes.string,
sponsorships: PropTypes.array,
sponsorshipIds: PropTypes.array,
summitId: PropTypes.number,
sponsorId: PropTypes.number
};

const mapStateToProps = ({ currentPageTemplateState }) => ({
...currentPageTemplateState
});

export default connect(mapStateToProps, {})(PageTemplatePopup);
export default PageTemplatePopup;
Loading