From 139ceb847ff080e11b13ce318a90e40807165ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Sun, 1 Feb 2026 03:23:03 -0300 Subject: [PATCH 1/3] feat: add edit popup on sponsor pages list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/page-template-actions.js | 7 +- src/actions/sponsor-pages-actions.js | 122 +++++++++++++++++- src/i18n/en.json | 2 + .../page-templates/page-template-list-page.js | 4 +- .../page-templates/page-template-popup.js | 22 +++- .../sponsors/sponsor-pages-list-page/index.js | 30 ++++- .../sponsors/sponsor-pages-list-reducer.js | 15 ++- 7 files changed, 186 insertions(+), 16 deletions(-) diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index 9283195ef..ee0d6d505 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -54,7 +54,10 @@ export const getPageTemplates = orderDir = DEFAULT_ORDER_DIR, hideArchived = false ) => - async (dispatch) => { + async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const summitTZ = currentSummit.time_zone.name; const accessToken = await getAccessTokenSafely(); const filter = []; @@ -92,7 +95,7 @@ export const getPageTemplates = createAction(RECEIVE_PAGE_TEMPLATES), `${window.SPONSOR_PAGES_API_URL}/api/v1/page-templates`, authErrorHandler, - { order, orderDir, page, perPage, term, hideArchived } + { order, orderDir, page, perPage, term, hideArchived, summitTZ } )(params)(dispatch).then(() => { dispatch(stopLoading()); }); diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index cb7355d1d..284b2beec 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -16,22 +16,30 @@ import { createAction, getRequest, postRequest, + putRequest, startLoading, stopLoading } from "openstack-uicore-foundation/lib/utils/actions"; import T from "i18n-react/dist/i18n-react"; +import moment from "moment-timezone"; import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; import { getSponsorForms } from "./sponsor-forms-actions"; import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, - DEFAULT_PER_PAGE + DEFAULT_PER_PAGE, + PAGES_MODULE_KINDS } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; export const REQUEST_SPONSOR_PAGES = "REQUEST_SPONSOR_PAGES"; export const RECEIVE_SPONSOR_PAGES = "RECEIVE_SPONSOR_PAGES"; +export const RECEIVE_SPONSOR_PAGE = "RECEIVE_SPONSOR_PAGE"; + +export const SPONSOR_PAGE_UPDATED = "SPONSOR_PAGE_UPDATED"; +export const SPONSOR_PAGE_ADDED = "SPONSOR_PAGE_ADDED"; + export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED"; export const getSponsorPages = @@ -93,6 +101,118 @@ export const getSponsorPages = }); }; +export const getSponsorPage = (pageId) => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return getRequest( + null, + createAction(RECEIVE_SPONSOR_PAGE), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/${pageId}`, + authErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + }); +}; + +const normalizeSponsorPage = (entity) => { + const normalizedEntity = { ...entity }; + + normalizedEntity.modules = entity.modules.map((module) => { + const normalizedModule = { ...module }; + + if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.upload_deadline) { + normalizedModule.upload_deadline = moment + .utc(module.upload_deadline) + .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; +}; + +export const saveSponsorPage = (entity) => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const { currentSummit } = currentSummitState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + const normalizedSponsorPage = normalizeSponsorPage(entity); + + if (entity.id) { + return putRequest( + null, + createAction(SPONSOR_PAGE_UPDATED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/${entity.id}`, + normalizedSponsorPage, + snackbarErrorHandler, + entity + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_pages.page_saved") + }) + ); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + dispatch(stopLoading()); + }); + } + + return postRequest( + null, + createAction(SPONSOR_PAGE_ADDED), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages`, + normalizedSponsorPage, + snackbarErrorHandler, + entity + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_pages.page_created") + }) + ); + }) + .catch((err) => { + console.error(err); + }) + .finally(() => { + dispatch(stopLoading()); + }); +}; + export const cloneGlobalPage = (pagesIds, sponsorIds, allSponsors) => async (dispatch, getState) => { const { currentSummitState } = getState(); diff --git a/src/i18n/en.json b/src/i18n/en.json index abea35a2c..c05e245c3 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2641,6 +2641,8 @@ "filter": "Filter", "sort_by": "Sort By", "no_sponsors_pages": "No pages found for this search criteria.", + "page_saved": "Page updated successfully.", + "page_created": "Page created successfully.", "placeholders": { "search": "Search..." }, diff --git a/src/pages/sponsors-global/page-templates/page-template-list-page.js b/src/pages/sponsors-global/page-templates/page-template-list-page.js index 34b09dd59..bedc55deb 100644 --- a/src/pages/sponsors-global/page-templates/page-template-list-page.js +++ b/src/pages/sponsors-global/page-templates/page-template-list-page.js @@ -49,7 +49,8 @@ const PageTemplateListPage = ({ archivePageTemplate, unarchivePageTemplate, savePageTemplate, - deletePageTemplate + deletePageTemplate, + summitTZ }) => { const [pageTemplateId, setPageTemplateId] = useState(null); @@ -266,6 +267,7 @@ const PageTemplateListPage = ({ open={!!pageTemplateId} onClose={() => setPageTemplateId(null)} onSave={handleSavePageTemplate} + summitTZ={summitTZ} /> ); diff --git a/src/pages/sponsors-global/page-templates/page-template-popup.js b/src/pages/sponsors-global/page-templates/page-template-popup.js index 586d76919..f51422819 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup.js @@ -18,6 +18,7 @@ import AddIcon from "@mui/icons-material/Add"; import CloseIcon from "@mui/icons-material/Close"; import { FormikProvider, useFormik } from "formik"; import * as yup from "yup"; +import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import MuiFormikTextField from "../../../components/mui/formik-inputs/mui-formik-textfield"; import PageModules from "./page-template-modules-form"; import { @@ -25,7 +26,13 @@ import { PAGE_MODULES_MEDIA_TYPES } from "../../../utils/constants"; -const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { +const PageTemplatePopup = ({ + pageTemplate, + open, + onClose, + onSave, + summitTZ +}) => { const handleClose = () => { onClose(); }; @@ -108,10 +115,21 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { } }); + const normalizeModules = (modules = [], summitTZ = "UTC") => + modules.map((m) => { + if (m.kind === PAGES_MODULE_KINDS.MEDIA && m.upload_deadline) { + return { + ...m, + upload_deadline: epochToMomentTimeZone(m.upload_deadline, summitTZ) + }; + } + return m; + }); + const formik = useFormik({ initialValues: { ...pageTemplate, - modules: pageTemplate?.modules || [] + modules: normalizeModules(pageTemplate?.modules, summitTZ) || [] }, validationSchema: yup.object().shape({ code: yup.string().required(T.translate("validation.required")), diff --git a/src/pages/sponsors/sponsor-pages-list-page/index.js b/src/pages/sponsors/sponsor-pages-list-page/index.js index b453cb6ad..06e9d06c2 100644 --- a/src/pages/sponsors/sponsor-pages-list-page/index.js +++ b/src/pages/sponsors/sponsor-pages-list-page/index.js @@ -23,11 +23,15 @@ import { Grid2 } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; -import { getSponsorPages } from "../../../actions/sponsor-pages-actions"; -import { getSponsorForm } from "../../../actions/sponsor-forms-actions"; +import { + getSponsorPages, + getSponsorPage, + saveSponsorPage +} from "../../../actions/sponsor-pages-actions"; import CustomAlert from "../../../components/mui/custom-alert"; import MuiTable from "../../../components/mui/table/mui-table"; import GlobalPagePopup from "./components/global-page/global-page-popup"; +import PageTemplatePopup from "../../sponsors-global/page-templates/page-template-popup"; const SponsorPagesListPage = ({ sponsorPages, @@ -38,8 +42,10 @@ const SponsorPagesListPage = ({ orderDir, hideArchived, totalCount, + currentSponsorPage, getSponsorPages, - getSponsorForm + getSponsorPage, + saveSponsorPage }) => { const [openPopup, setOpenPopup] = useState(null); @@ -67,7 +73,7 @@ const SponsorPagesListPage = ({ }; const handleRowEdit = (row) => { - getSponsorForm(row.id).then(() => { + getSponsorPage(row.id).then(() => { setOpenPopup("new"); }); }; @@ -93,6 +99,13 @@ const SponsorPagesListPage = ({ ); }; + const handleSaveSponsorPage = (entity) => { + saveSponsorPage(entity).then(() => { + setOpenPopup(null); + getSponsorPages(); + }); + }; + const columns = [ { columnKey: "code", @@ -216,10 +229,12 @@ const SponsorPagesListPage = ({ open={openPopup === "clone"} onClose={() => setOpenPopup(null)} /> - {/* setOpenPopup(null)} - /> */} + onSave={handleSaveSponsorPage} + /> ); }; @@ -230,5 +245,6 @@ const mapStateToProps = ({ sponsorPagesListState }) => ({ export default connect(mapStateToProps, { getSponsorPages, - getSponsorForm + getSponsorPage, + saveSponsorPage })(SponsorPagesListPage); diff --git a/src/reducers/sponsors/sponsor-pages-list-reducer.js b/src/reducers/sponsors/sponsor-pages-list-reducer.js index 41c4f2a43..dd2241aba 100644 --- a/src/reducers/sponsors/sponsor-pages-list-reducer.js +++ b/src/reducers/sponsors/sponsor-pages-list-reducer.js @@ -13,6 +13,7 @@ import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { + RECEIVE_SPONSOR_PAGE, RECEIVE_SPONSOR_PAGES, REQUEST_SPONSOR_PAGES } from "../../actions/sponsor-pages-actions"; @@ -28,7 +29,9 @@ const DEFAULT_STATE = { lastPage: 1, perPage: 10, totalCount: 0, - hideArchived: false + hideArchived: false, + currentSponsorPage: null, + summitTZ: null }; const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { @@ -40,7 +43,7 @@ const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { return DEFAULT_STATE; } case REQUEST_SPONSOR_PAGES: { - const { order, orderDir, page, term, hideArchived } = payload; + const { order, orderDir, page, term, hideArchived, summitTZ } = payload; return { ...state, @@ -49,7 +52,8 @@ const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { sponsorPages: [], currentPage: page, term, - hideArchived + hideArchived, + summitTZ }; } case RECEIVE_SPONSOR_PAGES: { @@ -82,6 +86,11 @@ const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { lastPage }; } + case RECEIVE_SPONSOR_PAGE: { + const sponsorPage = payload.response; + + return { ...state, currentSponsorPage: sponsorPage }; + } default: return state; } From afae88a3547db3cf48e48a3e0c13b24087f2d913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 2 Feb 2026 18:25:43 -0300 Subject: [PATCH 2/3] fix: adjust initial value for file types on media modules at edit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-pages-actions.js | 3 ++- .../page-templates/page-template-popup.js | 20 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 284b2beec..2cbf0513e 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -109,7 +109,8 @@ export const getSponsorPage = (pageId) => async (dispatch, getState) => { dispatch(startLoading()); const params = { - access_token: accessToken + access_token: accessToken, + expand: "modules.file_type" }; return getRequest( diff --git a/src/pages/sponsors-global/page-templates/page-template-popup.js b/src/pages/sponsors-global/page-templates/page-template-popup.js index f51422819..38440b692 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup.js @@ -117,11 +117,21 @@ const PageTemplatePopup = ({ const normalizeModules = (modules = [], summitTZ = "UTC") => modules.map((m) => { - if (m.kind === PAGES_MODULE_KINDS.MEDIA && m.upload_deadline) { - return { - ...m, - upload_deadline: epochToMomentTimeZone(m.upload_deadline, summitTZ) - }; + if (m.kind === PAGES_MODULE_KINDS.MEDIA) { + const normalizeModule = { ...m }; + if (m.upload_deadline) { + normalizeModule.upload_deadline = epochToMomentTimeZone( + m.upload_deadline, + summitTZ + ); + } + if (m.file_type) { + normalizeModule.file_type_id = { + value: m.file_type.id, + label: `${m.file_type.name} (${m.file_type.allowed_extensions})` + }; + } + return normalizeModule; } return m; }); From c15f28ab2110b02f7a981bee0f2e8af137c6254d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 2 Feb 2026 18:52:04 -0300 Subject: [PATCH 3/3] fix: reset form on close for sponsor page 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-pages-actions.js | 5 +++++ .../sponsors/sponsor-pages-list-page/index.js | 16 ++++++++++++---- .../sponsors/sponsor-pages-list-reducer.js | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js index 2cbf0513e..6124fc2ea 100644 --- a/src/actions/sponsor-pages-actions.js +++ b/src/actions/sponsor-pages-actions.js @@ -39,6 +39,7 @@ export const RECEIVE_SPONSOR_PAGE = "RECEIVE_SPONSOR_PAGE"; export const SPONSOR_PAGE_UPDATED = "SPONSOR_PAGE_UPDATED"; export const SPONSOR_PAGE_ADDED = "SPONSOR_PAGE_ADDED"; +export const RESET_SPONSOR_PAGE_FORM = "RESET_SPONSOR_PAGE_FORM"; export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED"; @@ -214,6 +215,10 @@ export const saveSponsorPage = (entity) => async (dispatch, getState) => { }); }; +export const resetSponsorPageForm = () => (dispatch) => { + dispatch(createAction(RESET_SPONSOR_PAGE_FORM)({})); +}; + export const cloneGlobalPage = (pagesIds, sponsorIds, allSponsors) => async (dispatch, getState) => { const { currentSummitState } = getState(); diff --git a/src/pages/sponsors/sponsor-pages-list-page/index.js b/src/pages/sponsors/sponsor-pages-list-page/index.js index 06e9d06c2..142161c33 100644 --- a/src/pages/sponsors/sponsor-pages-list-page/index.js +++ b/src/pages/sponsors/sponsor-pages-list-page/index.js @@ -26,7 +26,8 @@ import AddIcon from "@mui/icons-material/Add"; import { getSponsorPages, getSponsorPage, - saveSponsorPage + saveSponsorPage, + resetSponsorPageForm } from "../../../actions/sponsor-pages-actions"; import CustomAlert from "../../../components/mui/custom-alert"; import MuiTable from "../../../components/mui/table/mui-table"; @@ -45,7 +46,8 @@ const SponsorPagesListPage = ({ currentSponsorPage, getSponsorPages, getSponsorPage, - saveSponsorPage + saveSponsorPage, + resetSponsorPageForm }) => { const [openPopup, setOpenPopup] = useState(null); @@ -106,6 +108,11 @@ const SponsorPagesListPage = ({ }); }; + const handleTemplatePopupClose = () => { + resetSponsorPageForm(); + setOpenPopup(null); + }; + const columns = [ { columnKey: "code", @@ -232,7 +239,7 @@ const SponsorPagesListPage = ({ setOpenPopup(null)} + onClose={handleTemplatePopupClose} onSave={handleSaveSponsorPage} /> @@ -246,5 +253,6 @@ const mapStateToProps = ({ sponsorPagesListState }) => ({ export default connect(mapStateToProps, { getSponsorPages, getSponsorPage, - saveSponsorPage + saveSponsorPage, + resetSponsorPageForm })(SponsorPagesListPage); diff --git a/src/reducers/sponsors/sponsor-pages-list-reducer.js b/src/reducers/sponsors/sponsor-pages-list-reducer.js index dd2241aba..41dc775e4 100644 --- a/src/reducers/sponsors/sponsor-pages-list-reducer.js +++ b/src/reducers/sponsors/sponsor-pages-list-reducer.js @@ -15,11 +15,18 @@ import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { RECEIVE_SPONSOR_PAGE, RECEIVE_SPONSOR_PAGES, - REQUEST_SPONSOR_PAGES + REQUEST_SPONSOR_PAGES, + RESET_SPONSOR_PAGE_FORM } from "../../actions/sponsor-pages-actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { PAGES_MODULE_KINDS } from "../../utils/constants"; +const DEFAULT_SPONSOR_PAGE = { + code: "", + name: "", + modules: [] +}; + const DEFAULT_STATE = { sponsorPages: [], term: "", @@ -30,7 +37,7 @@ const DEFAULT_STATE = { perPage: 10, totalCount: 0, hideArchived: false, - currentSponsorPage: null, + currentSponsorPage: DEFAULT_SPONSOR_PAGE, summitTZ: null }; @@ -91,6 +98,9 @@ const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => { return { ...state, currentSponsorPage: sponsorPage }; } + case RESET_SPONSOR_PAGE_FORM: { + return { ...state, currentSponsorPage: DEFAULT_SPONSOR_PAGE }; + } default: return state; }