diff --git a/src/actions/sponsor-pages-actions.js b/src/actions/sponsor-pages-actions.js
new file mode 100644
index 000000000..e93e0042c
--- /dev/null
+++ b/src/actions/sponsor-pages-actions.js
@@ -0,0 +1,134 @@
+/**
+ * Copyright 2018 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 {
+ authErrorHandler,
+ createAction,
+ getRequest,
+ postRequest,
+ startLoading,
+ stopLoading
+} from "openstack-uicore-foundation/lib/utils/actions";
+import T from "i18n-react/dist/i18n-react";
+import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods";
+import {
+ DEFAULT_CURRENT_PAGE,
+ DEFAULT_ORDER_DIR,
+ DEFAULT_PER_PAGE
+} 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 GLOBAL_PAGE_CLONED = "GLOBAL_PAGE_CLONED";
+
+export const getSponsorPages =
+ (
+ term = "",
+ page = DEFAULT_CURRENT_PAGE,
+ perPage = DEFAULT_PER_PAGE,
+ order = "id",
+ orderDir = DEFAULT_ORDER_DIR,
+ hideArchived = false,
+ sponsorshipTypesId = []
+ ) =>
+ async (dispatch, getState) => {
+ const { currentSummitState } = getState();
+ const { currentSummit } = currentSummitState;
+ const accessToken = await getAccessTokenSafely();
+ const filter = [];
+
+ dispatch(startLoading());
+
+ if (term) {
+ const escapedTerm = escapeFilterValue(term);
+ filter.push(`name=@${escapedTerm},code=@${escapedTerm}`);
+ }
+
+ const params = {
+ page,
+ per_page: perPage,
+ access_token: accessToken,
+ expand: "sponsorship_types"
+ };
+
+ if (hideArchived) filter.push("is_archived==0");
+
+ if (sponsorshipTypesId?.length > 0) {
+ const formattedSponsorships = sponsorshipTypesId.join("&&");
+ filter.push("applies_to_all_tiers==0");
+ filter.push(`sponsorship_type_id_not_in==${formattedSponsorships}`);
+ }
+
+ 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_SPONSOR_PAGES),
+ createAction(RECEIVE_SPONSOR_PAGES),
+ `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages`,
+ authErrorHandler,
+ { order, orderDir, page, term, hideArchived }
+ )(params)(dispatch).then(() => {
+ dispatch(stopLoading());
+ });
+ };
+
+export const cloneGlobalPage =
+ (pagesIds, sponsorIds, allSponsors) => async (dispatch, getState) => {
+ const { currentSummitState } = getState();
+ const accessToken = await getAccessTokenSafely();
+ const { currentSummit } = currentSummitState;
+
+ dispatch(startLoading());
+
+ const params = {
+ access_token: accessToken
+ };
+
+ const normalizedEntity = {
+ page_template_ids: pagesIds,
+ sponsorship_types: sponsorIds,
+ apply_to_all_types: allSponsors
+ };
+
+ if (allSponsors) {
+ delete normalizedEntity.sponsorship_types;
+ }
+
+ return postRequest(
+ null,
+ createAction(GLOBAL_PAGE_CLONED),
+ `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/show-pages/clone`,
+ normalizedEntity,
+ snackbarErrorHandler
+ )(params)(dispatch)
+ .then(() => {
+ dispatch(getSponsorForms());
+ dispatch(
+ snackbarSuccessHandler({
+ title: T.translate("general.success"),
+ html: T.translate("sponsor_pages.global_page_popup.success")
+ })
+ );
+ })
+ .catch(() => {}); // need to catch promise reject
+ };
diff --git a/src/components/menu/index.js b/src/components/menu/index.js
index 83e492498..0689c4f10 100644
--- a/src/components/menu/index.js
+++ b/src/components/menu/index.js
@@ -244,6 +244,11 @@ const getSummitItems = (summitId) => [
linkUrl: `summits/${summitId}/sponsors/forms`,
accessRoute: "admin-sponsors"
},
+ {
+ name: "sponsor_pages",
+ linkUrl: `summits/${summitId}/sponsors/pages`,
+ accessRoute: "admin-sponsors"
+ },
{
name: "sponsorship_list",
linkUrl: `summits/${summitId}/sponsorships`,
diff --git a/src/i18n/en.json b/src/i18n/en.json
index e6ca66683..0161f0b2d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -154,6 +154,7 @@
"sponsors": "Sponsors",
"sponsor_list": "Sponsor List",
"sponsor_forms": "Forms",
+ "sponsor_pages": "Pages",
"sponsorship_list": "Tiers",
"sponsor_users": "Users",
"sponsors_promocodes": "Promo Codes",
@@ -2622,6 +2623,40 @@
"items_added": "Items added successfully."
}
},
+ "sponsor_pages": {
+ "pages": "Pages",
+ "alert_info": "Note: These Pages will be visible to all sponsors of the selected level in this program.",
+ "using_template": "Using Template",
+ "new_page": "New Page",
+ "code_column_label": "Code",
+ "name_column_label": "Name",
+ "tier_column_label": "Tier",
+ "info_mod_column_label": "Info Mod",
+ "upload_mod_column_label": "Upload Mod",
+ "download_mod_column_label": "Download Mod",
+ "hide_archived": "Hide archived Pages",
+ "filter": "Filter",
+ "sort_by": "Sort By",
+ "no_sponsors_pages": "No pages found for this search criteria.",
+ "placeholders": {
+ "search": "Search..."
+ },
+ "global_page_popup": {
+ "title": "Add Page Template",
+ "items_selected": "items selected",
+ "code": "Code",
+ "name": "name",
+ "info_mod": "Info Mod",
+ "download_mod": "Download Mod",
+ "upload_mod": "Upload Mod",
+ "add_selected": "Add Selected Page Template",
+ "success": "Page created successfully.",
+ "error": "There was a problem creating the forms, please try again.",
+ "placeholders": {
+ "search": "Search..."
+ }
+ }
+ },
"sponsor_users": {
"users": "Users",
"access_request": "access request",
diff --git a/src/layouts/sponsor-layout.js b/src/layouts/sponsor-layout.js
index fefd4f28d..85ed8dbb9 100644
--- a/src/layouts/sponsor-layout.js
+++ b/src/layouts/sponsor-layout.js
@@ -25,6 +25,7 @@ import SponsorSettingsPage from "../pages/sponsor_settings/sponsor-settings-page
import SponsorFormsListPage from "../pages/sponsors/sponsor-forms-list-page";
import SponsorFormItemListPage from "../pages/sponsors/sponsor-form-item-list-page";
import SponsorUsersListPage from "../pages/sponsors/sponsor-users-list-page";
+import sponsorPagesListPage from "../pages/sponsors/sponsor-pages-list-page";
const SponsorLayout = ({ match }) => (
@@ -63,6 +64,27 @@ const SponsorLayout = ({ match }) => (
)}
/>
+ (
+
+
+
+
+
+
+ )}
+ />
{
+ const [stage, setStage] = useState("pages");
+ const [selectedTemplates, setSelectedTemplates] = useState([]);
+ const dialogSize = stage === "pages" ? "md" : "sm";
+
+ const handleClose = () => {
+ setSelectedTemplates([]);
+ setStage("pages");
+ onClose();
+ };
+
+ const handleOnSelectTemplates = (templates) => {
+ setSelectedTemplates(templates);
+ setStage("sponsorships");
+ };
+
+ const handleOnSave = (selectedTiers, allTiers) => {
+ cloneGlobalPage(selectedTemplates, selectedTiers, allTiers).finally(() => {
+ handleClose();
+ });
+ };
+
+ return (
+
+ );
+};
+
+GlobalPagePopup.propTypes = {
+ open: PropTypes.bool.isRequired,
+ onClose: PropTypes.func.isRequired
+};
+
+const mapStateToProps = () => ({});
+
+export default connect(mapStateToProps, {
+ cloneGlobalPage
+})(GlobalPagePopup);
diff --git a/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js b/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js
new file mode 100644
index 000000000..2a8e4a2b4
--- /dev/null
+++ b/src/pages/sponsors/sponsor-pages-list-page/components/global-page/select-pages-dialog.js
@@ -0,0 +1,191 @@
+import React, { useEffect, useState } from "react";
+import T from "i18n-react/dist/i18n-react";
+import PropTypes from "prop-types";
+import { connect } from "react-redux";
+import {
+ Box,
+ Button,
+ Checkbox,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ FormControlLabel,
+ Grid2,
+ IconButton,
+ Typography
+} from "@mui/material";
+import CloseIcon from "@mui/icons-material/Close";
+import SearchInput from "../../../../../components/mui/search-input";
+import { getPageTemplates } from "../../../../../actions/page-template-actions";
+import { DEFAULT_PER_PAGE } from "../../../../../utils/constants";
+import MuiInfiniteTable from "../../../../../components/mui/infinite-table";
+
+const SelectPagesDialog = ({
+ pageTemplates,
+ items,
+ currentPage,
+ term,
+ order,
+ orderDir,
+ total,
+ onSave,
+ onClose,
+ getPageTemplates
+}) => {
+ const [selectedRows, setSelectedRows] = useState([]);
+
+ useEffect(() => {
+ getPageTemplates("", 1, DEFAULT_PER_PAGE, "id", 1, true);
+ }, []);
+
+ const handleSort = (key, dir) => {
+ getPageTemplates(term, 1, DEFAULT_PER_PAGE, key, dir, true);
+ };
+
+ const handleLoadMore = () => {
+ if (total > items.length) {
+ getPageTemplates(
+ term,
+ currentPage + 1,
+ DEFAULT_PER_PAGE,
+ order,
+ orderDir,
+ true
+ );
+ }
+ };
+
+ const handleClose = () => {
+ setSelectedRows([]);
+ onClose();
+ };
+
+ const handleOnCheck = (rowId, checked) => {
+ if (checked) {
+ setSelectedRows([...selectedRows, rowId]);
+ } else {
+ setSelectedRows(selectedRows.filter((r) => r !== rowId));
+ }
+ };
+
+ const handleOnSearch = (searchTerm) => {
+ getPageTemplates(searchTerm, 1, DEFAULT_PER_PAGE, "id", 1, true);
+ };
+
+ const handleOnSave = () => {
+ onSave(selectedRows);
+ };
+
+ const columns = [
+ {
+ columnKey: "select",
+ header: "",
+ width: 30,
+ align: "center",
+ render: (row) => (
+ handleOnCheck(row.id, ev.target.checked)}
+ />
+ }
+ />
+ )
+ },
+ {
+ columnKey: "code",
+ header: T.translate("sponsor_pages.global_page_popup.code"),
+ sortable: true
+ },
+ {
+ columnKey: "name",
+ header: T.translate("sponsor_pages.global_page_popup.name"),
+ sortable: true
+ },
+ {
+ columnKey: "info_mod",
+ header: T.translate("sponsor_pages.global_page_popup.info_mod"),
+ sortable: false
+ },
+ {
+ columnKey: "download_mod",
+ header: T.translate("sponsor_pages.global_page_popup.download_mod"),
+ sortable: false
+ },
+ {
+ columnKey: "upload_mod",
+ header: T.translate("sponsor_pages.global_page_popup.upload_mod"),
+ sortable: false
+ }
+ ];
+
+ return (
+ <>
+
+
+ {T.translate("sponsor_pages.global_page_popup.title")}
+
+ handleClose()}>
+
+
+
+
+
+
+
+ {selectedRows.length} items selected
+
+
+
+
+
+
+ {pageTemplates.length > 0 && (
+
+
+
+ )}
+
+
+
+
+
+ >
+ );
+};
+
+SelectPagesDialog.propTypes = {
+ onClose: PropTypes.func.isRequired,
+ onSave: PropTypes.func.isRequired
+};
+
+const mapStateToProps = ({ pageTemplateListState }) => ({
+ ...pageTemplateListState
+});
+
+export default connect(mapStateToProps, {
+ getPageTemplates
+})(SelectPagesDialog);
diff --git a/src/pages/sponsors/sponsor-pages-list-page/index.js b/src/pages/sponsors/sponsor-pages-list-page/index.js
new file mode 100644
index 000000000..ad50767a8
--- /dev/null
+++ b/src/pages/sponsors/sponsor-pages-list-page/index.js
@@ -0,0 +1,223 @@
+/**
+ * Copyright 2024 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 { connect } from "react-redux";
+import T from "i18n-react/dist/i18n-react";
+import {
+ Box,
+ Button,
+ Checkbox,
+ FormControlLabel,
+ FormGroup,
+ Grid2
+} from "@mui/material";
+import AddIcon from "@mui/icons-material/Add";
+import { getSponsorPages } 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";
+
+const SponsorPagesListPage = ({
+ sponsorPages,
+ currentPage,
+ perPage,
+ term,
+ order,
+ orderDir,
+ hideArchived,
+ totalCount,
+ getSponsorPages,
+ getSponsorForm
+}) => {
+ const [openPopup, setOpenPopup] = useState(null);
+
+ useEffect(() => {
+ getSponsorPages();
+ }, []);
+
+ const handlePageChange = (page) => {
+ getSponsorPages(term, page, perPage, order, orderDir, hideArchived);
+ };
+
+ const handleSort = (key, dir) => {
+ getSponsorPages(term, currentPage, perPage, key, dir, hideArchived);
+ };
+
+ const handleRowEdit = (row) => {
+ getSponsorForm(row.id).then(() => {
+ setOpenPopup("new");
+ });
+ };
+
+ const handleRowDelete = (itemId) => {
+ console.log("DELETE ITEM ID...", itemId);
+ // deleteSponsorForm(itemId);
+ };
+
+ const handleArchiveItem = (item) => console.log("archive ITEM...", item);
+ // item.is_archived
+ // ? unarchiveSponsorForm(item.id)
+ // : archiveSponsorForm(item.id);
+
+ const handleHideArchivedForms = (ev) => {
+ getSponsorPages(
+ term,
+ currentPage,
+ perPage,
+ order,
+ orderDir,
+ ev.target.checked
+ );
+ };
+
+ const columns = [
+ {
+ columnKey: "code",
+ header: T.translate("sponsor_pages.code_column_label"),
+ sortable: true
+ },
+ {
+ columnKey: "name",
+ header: T.translate("sponsor_pages.name_column_label"),
+ sortable: true
+ },
+ {
+ columnKey: "tier",
+ header: T.translate("sponsor_pages.tier_column_label"),
+ sortable: false
+ },
+ {
+ columnKey: "info_mod",
+ header: T.translate("sponsor_pages.info_mod_column_label"),
+ sortable: false
+ },
+ {
+ columnKey: "upload_mod",
+ header: T.translate("sponsor_pages.upload_mod_column_label"),
+ sortable: false
+ },
+ {
+ columnKey: "download_mod",
+ header: T.translate("sponsor_pages.download_mod_column_label"),
+ sortable: false
+ }
+ ];
+
+ const tableOptions = {
+ sortCol: order,
+ sortDir: orderDir,
+ disableProp: "is_archived"
+ };
+
+ return (
+
+
{T.translate("sponsor_pages.pages")}
+
+
+
+
+ {totalCount} {T.translate("sponsor_pages.pages")}
+
+
+
+
+
+ }
+ label={T.translate("sponsor_pages.hide_archived")}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ {sponsorPages.length === 0 && (
+
{T.translate("sponsor_pages.no_sponsors_pages")}
+ )}
+
+ {sponsorPages.length > 0 && (
+
+
+
+ )}
+
+
setOpenPopup(null)}
+ />
+ {/* setOpenPopup(null)}
+ /> */}
+
+ );
+};
+
+const mapStateToProps = ({ sponsorPagesListState }) => ({
+ ...sponsorPagesListState
+});
+
+export default connect(mapStateToProps, {
+ getSponsorPages
+})(SponsorPagesListPage);
diff --git a/src/reducers/sponsors/sponsor-pages-list-reducer.js b/src/reducers/sponsors/sponsor-pages-list-reducer.js
new file mode 100644
index 000000000..c835667b0
--- /dev/null
+++ b/src/reducers/sponsors/sponsor-pages-list-reducer.js
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2019 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 { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions";
+import {
+ RECEIVE_SPONSOR_PAGES,
+ REQUEST_SPONSOR_PAGES
+} from "../../actions/sponsor-pages-actions";
+import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions";
+
+const DEFAULT_STATE = {
+ sponsorPages: [],
+ term: "",
+ order: "name",
+ orderDir: 1,
+ currentPage: 1,
+ lastPage: 1,
+ perPage: 10,
+ totalCount: 0,
+ hideArchived: false
+};
+
+const sponsorPagesListReducer = (state = DEFAULT_STATE, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case SET_CURRENT_SUMMIT:
+ case LOGOUT_USER: {
+ return DEFAULT_STATE;
+ }
+ case REQUEST_SPONSOR_PAGES: {
+ const { order, orderDir, page, term, hideArchived } = payload;
+
+ return {
+ ...state,
+ order,
+ orderDir,
+ sponsorPages: [],
+ currentPage: page,
+ term,
+ hideArchived
+ };
+ }
+ case RECEIVE_SPONSOR_PAGES: {
+ const {
+ current_page: currentPage,
+ total,
+ last_page: lastPage
+ } = payload.response;
+
+ const sponsorPages = payload.response.data.map((a) => ({
+ id: a.id,
+ code: a.code,
+ name: a.name,
+ tier: a.sponsorship_types.map((s) => s.name).join(", "),
+ info_mod: a.modules.filter((m) => m.kind === "Info").length,
+ upload_mod: a.modules.filter((m) => m.kind === "Upload").length,
+ download_mod: a.modules.filter((m) => m.kind === "Download").length,
+ is_archived: a.is_archived
+ }));
+
+ return {
+ ...state,
+ sponsorPages,
+ currentPage,
+ totalCount: total,
+ lastPage
+ };
+ }
+ default:
+ return state;
+ }
+};
+
+export default sponsorPagesListReducer;
diff --git a/src/store.js b/src/store.js
index b3289558e..ae9386539 100644
--- a/src/store.js
+++ b/src/store.js
@@ -167,6 +167,7 @@ import sponsorPageFormsListReducer from "./reducers/sponsors/sponsor-page-forms-
import sponsorCustomizedFormReducer from "./reducers/sponsors/sponsor-customized-form-reducer.js";
import sponsorPageCartListReducer from "./reducers/sponsors/sponsor-page-cart-list-reducer";
import sponsorCustomizedFormItemsListReducer from "./reducers/sponsors/sponsor-customized-form-items-list-reducer.js";
+import sponsorPagesListReducer from "./reducers/sponsors/sponsor-pages-list-reducer.js";
// default: localStorage if web, AsyncStorage if react-native
@@ -250,6 +251,7 @@ const reducers = persistCombineReducers(config, {
currentSponsorState: sponsorReducer,
sponsorFormsListState: sponsorFormsListReducer,
sponsorFormItemsListState: sponsorFormItemsListReducer,
+ sponsorPagesListState: sponsorPagesListReducer,
sponsorUsersListState: sponsorUsersListReducer,
sponsorPageFormsListState: sponsorPageFormsListReducer,
sponsorPageCartListState: sponsorPageCartListReducer,