diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js
index 78091a090..13667ace3 100644
--- a/src/actions/sponsor-pages-actions.js
+++ b/src/actions/sponsor-pages-actions.js
@@ -23,12 +23,17 @@ import {
import T from "i18n-react/dist/i18n-react";
import { getAccessTokenSafely } from "../utils/methods";
import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions";
-import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE } from "../utils/constants";
+import {
+ DEFAULT_CURRENT_PAGE,
+ DEFAULT_ORDER_DIR,
+ DEFAULT_PER_PAGE
+} from "../utils/constants";
export const GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED";
export const REQUEST_SPONSOR_MANAGED_PAGES = "REQUEST_SPONSOR_MANAGED_PAGES";
export const RECEIVE_SPONSOR_MANAGED_PAGES = "RECEIVE_SPONSOR_MANAGED_PAGES";
+export const SPONSOR_MANAGED_PAGE_ADDED = "SPONSOR_MANAGED_PAGE_ADDED";
export const REQUEST_SPONSOR_CUSTOMIZED_PAGES =
"REQUEST_SPONSOR_CUSTOMIZED_PAGES";
@@ -135,6 +140,49 @@ export const getSponsorManagedPages =
});
};
+export const saveSponsorManagedPage =
+ (entity) => async (dispatch, getState) => {
+ const { currentSummitState, currentSponsorState } = getState();
+ const { currentSummit } = currentSummitState;
+ const {
+ entity: { id: sponsorId }
+ } = currentSponsorState;
+ const accessToken = await getAccessTokenSafely();
+
+ dispatch(startLoading());
+
+ const normalizedEntity = normalizeSponsorManagedPage(entity);
+
+ const params = {
+ access_token: accessToken,
+ fields: "id,code,name,kind,modules_count,allowed_add_ons"
+ };
+
+ return postRequest(
+ null,
+ createAction(SPONSOR_MANAGED_PAGE_ADDED),
+ `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/managed-pages`,
+ normalizedEntity,
+ snackbarErrorHandler
+ )(params)(dispatch).finally(() => {
+ dispatch(stopLoading());
+ });
+ };
+
+const normalizeSponsorManagedPage = (entity) => {
+ const normalizedEntity = {
+ show_page_ids: entity.pages,
+ allowed_add_ons: entity.add_ons.map((a) => a.id),
+ apply_to_all_add_ons: false
+ };
+
+ if (entity.add_ons.includes("all")) {
+ normalizedEntity.apply_to_all_add_ons = true;
+ normalizedEntity.allowed_add_ons = [];
+ }
+
+ return normalizedEntity;
+};
/* ************************************************************************ */
/* CUSTOMIZED PAGES */
/* ************************************************************************ */
diff --git a/src/components/mui/formik-inputs/mui-formik-select-group.js b/src/components/mui/formik-inputs/mui-formik-select-group.js
index 9a1261d0a..60be7c1df 100644
--- a/src/components/mui/formik-inputs/mui-formik-select-group.js
+++ b/src/components/mui/formik-inputs/mui-formik-select-group.js
@@ -44,6 +44,7 @@ const MuiFormikSelectGroup = ({
selectAllLabel = "Select All",
getOptionLabel = (item) => item.name,
getOptionValue = (item) => item.id,
+ noOptionsLabel = "No items",
getGroupId = null,
getGroupLabel = null,
disabled = false
@@ -213,6 +214,79 @@ const MuiFormikSelectGroup = ({
.flat()
.filter(Boolean);
+ const renderMenuContent = () => {
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (options.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+ {showSelectAll && (
+ <>
+
+
+ >
+ )}
+ {renderGroupedOptions()}
+ >
+ );
+ };
+
const IconWithLoading = useMemo(() => getCustomIcon(loading), [loading]);
return (
@@ -243,54 +317,7 @@ const MuiFormikSelectGroup = ({
error={Boolean(error)}
IconComponent={IconWithLoading}
>
- {loading ? (
-
- ) : (
- <>
- {showSelectAll && options.length > 0 && (
- <>
-
-
- >
- )}
- {renderGroupedOptions()}
- >
- )}
+ {renderMenuContent()}
{error && (
handleSort("date", "+")
// },
// { label: "Newest", onClick: () => handleSort("date", "+") },
- { label: "A-Z", onClick: () => handleSort("name", 1) },
- { label: "Z-A", onClick: () => handleSort("name", 0) }
+ {
+ label: T.translate("general.sort_asc_label"),
+ onClick: () => handleSort("name", 1)
+ },
+ {
+ label: T.translate("general.sort_desc_label"),
+ onClick: () => handleSort("name", 0)
+ }
]}
>
-
sort by
+
{" "}
+ {T.translate("general.sort_by")}
diff --git a/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js b/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js
index f9a2336d4..67dcf3f10 100644
--- a/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js
+++ b/src/pages/sponsors-global/form-templates/form-template-from-duplicate-popup.js
@@ -119,11 +119,18 @@ const FormTemplateFromDuplicateDialog = ({
buttonSx={{ color: "#000" }}
menuItems={[
// { label: "Newest", onClick: () => handleSort("+date") },
- { label: "A-Z", onClick: () => handleSort("name", 1) },
- { label: "Z-A", onClick: () => handleSort("name", 0) }
+ {
+ label: T.translate("general.sort_asc_label"),
+ onClick: () => handleSort("name", 1)
+ },
+ {
+ label: T.translate("general.sort_desc_label"),
+ onClick: () => handleSort("name", 0)
+ }
]}
>
- sort by
+ {" "}
+ {T.translate("general.sort_by")}
diff --git a/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js b/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js
index 3a6ff11f7..77d604d3e 100644
--- a/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js
+++ b/src/pages/sponsors/sponsor-forms-tab/components/add-sponsor-form-template-popup/index.js
@@ -227,11 +227,18 @@ const AddSponsorFormTemplatePopup = ({
buttonId="sort-button"
menuId="sort-menu"
menuItems={[
- { label: "A-Z", onClick: () => handleSort("name", 1) },
- { label: "Z-A", onClick: () => handleSort("name", 0) }
+ {
+ label: T.translate("general.sort_asc_label"),
+ onClick: () => handleSort("name", 1)
+ },
+ {
+ label: T.translate("general.sort_desc_label"),
+ onClick: () => handleSort("name", 0)
+ }
]}
>
- sort by
+ {" "}
+ {T.translate("general.sort_by")}
diff --git a/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js b/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js
index 8bb4643eb..f8b8fd61c 100644
--- a/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js
+++ b/src/pages/sponsors/sponsor-forms-tab/components/manage-items/sponsor-form-item-from-inventory.js
@@ -220,7 +220,8 @@ const SponsorFormItemFromInventoryPopup = ({
}
]}
>
- sort by
+ {" "}
+ {T.translate("general.sort_by")}
diff --git a/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js b/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js
new file mode 100644
index 000000000..80ae664f6
--- /dev/null
+++ b/src/pages/sponsors/sponsor-pages-tab/components/add-sponsor-page-template-popup/index.js
@@ -0,0 +1,345 @@
+/**
+ * Copyright 2026 OpenStack Foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * */
+
+import React, { useEffect, useState } from "react";
+import T from "i18n-react/dist/i18n-react";
+import { connect } from "react-redux";
+import PropTypes from "prop-types";
+import {
+ Box,
+ Button,
+ Checkbox,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ FormControlLabel,
+ Grid2,
+ IconButton,
+ TextField,
+ Typography
+} from "@mui/material";
+import SwapVertIcon from "@mui/icons-material/SwapVert";
+import SearchIcon from "@mui/icons-material/Search";
+import CloseIcon from "@mui/icons-material/Close";
+import * as yup from "yup";
+import { FormikProvider, useFormik } from "formik";
+import MuiTable from "../../../../../components/mui/table/mui-table";
+import MenuButton from "../../../../../components/mui/menu-button";
+import { querySponsorAddons } from "../../../../../actions/sponsor-actions";
+import { getShowPages } from "../../../../../actions/show-pages-actions";
+import {
+ DEFAULT_CURRENT_PAGE,
+ FIVE_PER_PAGE
+} from "../../../../../utils/constants";
+import MuiFormikSelectGroup from "../../../../../components/mui/formik-inputs/mui-formik-select-group";
+
+const AddSponsorPageTemplatePopup = ({
+ onClose,
+ onSubmit,
+ showPages,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ totalCount,
+ term = "",
+ getShowPages,
+ sponsor,
+ summitId
+}) => {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [selectedPages, setSelectedPages] = useState([]);
+
+ const sponsorshipIds = sponsor.sponsorships.map((e) => e.id);
+
+ const sponsorshipTypeIds = sponsor.sponsorships.map((e) => e.type.id);
+
+ const formik = useFormik({
+ initialValues: {
+ add_ons: []
+ },
+ validationSchema: yup.object({
+ add_ons: yup
+ .array()
+ .test(
+ "add_ons-required",
+ T.translate("validation.add_on_required"),
+ (value) => value?.includes("all") || value?.length > 0
+ )
+ }),
+ onSubmit: (values) => {
+ const { add_ons } = values;
+ const entity = {
+ pages: selectedPages,
+ add_ons
+ };
+ onSubmit(entity);
+ },
+ enableReinitialize: true
+ });
+
+ useEffect(() => {
+ getShowPages(
+ term,
+ DEFAULT_CURRENT_PAGE,
+ FIVE_PER_PAGE,
+ order,
+ orderDir,
+ false,
+ sponsorshipTypeIds
+ );
+ }, []);
+
+ const handlePageChange = (page) => {
+ getShowPages(
+ term,
+ page,
+ FIVE_PER_PAGE,
+ order,
+ orderDir,
+ false,
+ sponsorshipTypeIds
+ );
+ };
+
+ const handleSort = (key, dir) => {
+ getShowPages(
+ term,
+ DEFAULT_CURRENT_PAGE,
+ FIVE_PER_PAGE,
+ key,
+ dir,
+ false,
+ sponsorshipTypeIds
+ );
+ };
+
+ const handleOnSearch = (ev) => {
+ if (ev.key === "Enter") {
+ ev.preventDefault();
+ ev.stopPropagation();
+ getShowPages(
+ searchTerm,
+ DEFAULT_CURRENT_PAGE,
+ perPage,
+ order,
+ orderDir,
+ false,
+ sponsorshipTypeIds
+ );
+ }
+ };
+
+ const handleSelected = (id, isSelected) => {
+ if (isSelected) {
+ setSelectedPages([...selectedPages, id]);
+ return;
+ }
+ const updatedSelected = selectedPages.filter((e) => e !== id);
+ setSelectedPages(updatedSelected);
+ };
+
+ const handleClose = () => {
+ onClose();
+ };
+
+ const tableOptions = {
+ sortCol: order,
+ sortDir: orderDir
+ };
+
+ const columns = [
+ {
+ columnKey: "select",
+ header: "",
+ width: 30,
+ align: "center",
+ render: (row) => (
+ handleSelected(row.id, ev.target.checked)}
+ />
+ }
+ />
+ )
+ },
+ {
+ columnKey: "code",
+ header: T.translate("edit_sponsor.pages_tab.code"),
+ sortable: false
+ },
+ {
+ columnKey: "name",
+ header: T.translate("edit_sponsor.pages_tab.name"),
+ sortable: false
+ },
+ {
+ columnKey: "info_mod",
+ header: T.translate("edit_sponsor.pages_tab.info_mod")
+ },
+ {
+ columnKey: "upload_mod",
+ header: T.translate("edit_sponsor.pages_tab.upload_mod")
+ },
+ {
+ columnKey: "download_mod",
+ header: T.translate("edit_sponsor.pages_tab.download_mod")
+ }
+ ];
+
+ return (
+
+ );
+};
+
+AddSponsorPageTemplatePopup.propTypes = {
+ onClose: PropTypes.func.isRequired
+};
+
+const mapStateToProps = ({ showPagesListState }) => ({
+ ...showPagesListState
+});
+
+export default connect(mapStateToProps, {
+ getShowPages
+})(AddSponsorPageTemplatePopup);
diff --git a/src/pages/sponsors/sponsor-pages-tab/index.js b/src/pages/sponsors/sponsor-pages-tab/index.js
index f14224c8f..5ca762e97 100644
--- a/src/pages/sponsors/sponsor-pages-tab/index.js
+++ b/src/pages/sponsors/sponsor-pages-tab/index.js
@@ -11,7 +11,7 @@
* limitations under the License.
* */
-import React, { useEffect } from "react";
+import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
import {
@@ -25,21 +25,28 @@ import {
import AddIcon from "@mui/icons-material/Add";
import {
getSponsorManagedPages,
- getSponsorCustomizedPages
+ getSponsorCustomizedPages,
+ saveSponsorManagedPage
} from "../../../actions/sponsor-pages-actions";
import CustomAlert from "../../../components/mui/custom-alert";
import SearchInput from "../../../components/mui/search-input";
import MuiTable from "../../../components/mui/table/mui-table";
import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants";
+import AddSponsorPageTemplatePopup from "./components/add-sponsor-page-template-popup";
const SponsorPagesTab = ({
+ sponsor,
+ summitId,
term,
hideArchived,
managedPages,
customizedPages,
getSponsorManagedPages,
- getSponsorCustomizedPages
+ getSponsorCustomizedPages,
+ saveSponsorManagedPage
}) => {
+ const [openPopup, setOpenPopup] = useState(null);
+
useEffect(() => {
getSponsorManagedPages();
getSponsorCustomizedPages();
@@ -140,6 +147,10 @@ const SponsorPagesTab = ({
);
};
+ const handleUsingTemplate = () => {
+ setOpenPopup("template");
+ };
+
const handleArchiveCustomizedPage = (item) =>
console.log("ARCHIVE CUSTOMIZED ", item);
@@ -181,6 +192,12 @@ const SponsorPagesTab = ({
);
};
+ const handleSaveManagedPageFromTemplate = (entity) => {
+ saveSponsorManagedPage(entity)
+ .then(() => getSponsorManagedPages())
+ .finally(() => setOpenPopup(null));
+ };
+
const baseColumns = (name) => [
{
columnKey: "name",
@@ -276,7 +293,7 @@ const SponsorPagesTab = ({
variant="contained"
size="medium"
fullWidth
- onClick={() => console.log("open popup template")}
+ onClick={handleUsingTemplate}
startIcon={}
sx={{ height: "36px" }}
>
@@ -336,6 +353,15 @@ const SponsorPagesTab = ({
onArchive={handleArchiveManagedPage}
/>
+
+ {openPopup === "template" && (
+ setOpenPopup(null)}
+ />
+ )}
);
};
@@ -346,5 +372,6 @@ const mapStateToProps = ({ sponsorPagePagesListState }) => ({
export default connect(mapStateToProps, {
getSponsorManagedPages,
+ saveSponsorManagedPage,
getSponsorCustomizedPages
})(SponsorPagesTab);