diff --git a/src/actions/page-template-actions.js b/src/actions/page-template-actions.js index 1fa0f7fab..63edee4a4 100644 --- a/src/actions/page-template-actions.js +++ b/src/actions/page-template-actions.js @@ -29,6 +29,7 @@ import { DEFAULT_CURRENT_PAGE, DEFAULT_ORDER_DIR, DEFAULT_PER_PAGE, + PAGE_MODULES_DOWNLOAD, PAGES_MODULE_KINDS } from "../utils/constants"; import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; @@ -48,7 +49,7 @@ export const PAGE_TEMPLATE_UNARCHIVED = "PAGE_TEMPLATE_UNARCHIVED"; export const getPageTemplates = ( - term = null, + term = "", page = DEFAULT_CURRENT_PAGE, perPage = DEFAULT_PER_PAGE, order = "id", @@ -106,7 +107,7 @@ export const getPageTemplate = (formTemplateId) => async (dispatch) => { const params = { access_token: accessToken, - expand: "materials,meta_fields,meta_fields.values" + expand: "modules" }; return getRequest( @@ -160,8 +161,14 @@ const normalizeEntity = (entity) => { module.file_type_id?.value || module.file_type_id; } - if (module.kind === PAGES_MODULE_KINDS.DOCUMENT && module.file) { - normalizedModule.file = module.file[0] || null; + if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { + if (module.type === PAGE_MODULES_DOWNLOAD.FILE) { + normalizedModule.file = module.file[0] || null; + delete normalizedModule.external_url; + } else { + delete normalizedModule.file; + delete normalizedModule.file_id; + } } delete normalizedModule._tempId; diff --git a/src/components/forms/media-upload-form.js b/src/components/forms/media-upload-form.js index 4210e4018..1c9890cc9 100644 --- a/src/components/forms/media-upload-form.js +++ b/src/components/forms/media-upload-form.js @@ -14,7 +14,7 @@ import React from "react"; import T from "i18n-react/dist/i18n-react"; import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; -import { Input, Dropdown } from "openstack-uicore-foundation/lib/components"; +import { Dropdown, Input } from "openstack-uicore-foundation/lib/components"; import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3"; import { isEmpty, scrollToError, shallowEqual } from "../../utils/methods"; @@ -103,6 +103,11 @@ class MediaUploadForm extends React.Component { .filter((t) => t.class_name === "PresentationType") .map((t) => ({ value: t.id, label: t.name })); + const mediaFileTypesDDL = mediaFileTypes.map((mft) => ({ + value: mft.id, + label: mft.name + })); + return (
@@ -183,7 +188,7 @@ class MediaUploadForm extends React.Component { className="right-space" value={entity.type_id} placeholder={T.translate("media_upload.placeholders.select_type")} - options={mediaFileTypes} + options={mediaFileTypesDDL} onChange={this.handleChange} /> diff --git a/src/components/inputs/formik-text-editor.js b/src/components/inputs/formik-text-editor.js index 0e2ce7527..7424bbbd0 100644 --- a/src/components/inputs/formik-text-editor.js +++ b/src/components/inputs/formik-text-editor.js @@ -1,23 +1,21 @@ 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 ( { const stringValue = normalizeHtmlString(e.target.value); - setFieldValue(name, stringValue); + helpers.setValue(stringValue); }} - onBlur={() => setFieldTouched(name, true)} - error={touched?.[name] && errors?.[name] ? errors?.[name] : ""} + error={meta.touched && meta.error} license={process.env.JODIT_LICENSE_KEY} {...props} /> diff --git a/src/components/mui/formik-inputs/mui-formik-datepicker.js b/src/components/mui/formik-inputs/mui-formik-datepicker.js index 13f85bcf9..25f403697 100644 --- a/src/components/mui/formik-inputs/mui-formik-datepicker.js +++ b/src/components/mui/formik-inputs/mui-formik-datepicker.js @@ -45,7 +45,7 @@ const MuiFormikDatepicker = ({ name, label, required, ...props }) => { MuiFormikDatepicker.propTypes = { name: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, + label: PropTypes.string, required: PropTypes.bool }; diff --git a/src/components/mui/formik-inputs/mui-formik-upload.js b/src/components/mui/formik-inputs/mui-formik-upload.js index 2cf33b854..432a98481 100644 --- a/src/components/mui/formik-inputs/mui-formik-upload.js +++ b/src/components/mui/formik-inputs/mui-formik-upload.js @@ -12,7 +12,7 @@ import { const MuiFormikUpload = ({ id, name, - onImageDeleted, + onDelete, maxFiles = MAX_INVENTORY_IMAGES_UPLOAD_QTY }) => { const [field, meta, helpers] = useField(name); @@ -59,8 +59,8 @@ const MuiFormikUpload = ({ (i) => i.filename !== imageFile.name ); helpers.setValue(updated); - if (onImageDeleted) { - onImageDeleted(imageFile.id); + if (onDelete) { + onDelete(imageFile.id); } }; @@ -91,7 +91,7 @@ const MuiFormikUpload = ({ MuiFormikUpload.propTypes = { id: PropTypes.string, name: PropTypes.string.isRequired, - onImageDeleted: PropTypes.func, + onDelete: PropTypes.func, maxFiles: PropTypes.number }; diff --git a/src/i18n/en.json b/src/i18n/en.json index e2a953b18..bea43d27b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3969,6 +3969,7 @@ "external_url": "External URL", "upload_file": "Upload File", "text_input": "Text input", + "input_url_link": "Input URL Link", "media_module": "Media Request Module", "name": "Name", "upload_deadline": "Upload Deadline", 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 473b5fc0c..cde747c7f 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 @@ -26,9 +26,10 @@ import { connect } from "react-redux"; import T from "i18n-react/dist/i18n-react"; import { archivePageTemplate, + deletePageTemplate, getPageTemplates, + getPageTemplate, savePageTemplate, - deletePageTemplate, unarchivePageTemplate } from "../../../actions/page-template-actions"; import MuiTable from "../../../components/mui/table/mui-table"; @@ -47,6 +48,7 @@ const PageTemplateListPage = ({ hideArchived, totalPageTemplates, getPageTemplates, + getPageTemplate, archivePageTemplate, unarchivePageTemplate, savePageTemplate, @@ -118,7 +120,7 @@ const PageTemplateListPage = ({ : archivePageTemplate(item.id); const handleEdit = (row) => { - console.log("EDIT", row); + getPageTemplate(row.id).then(() => setPageTemplateId(row.id)); }; const handleDelete = (row) => { @@ -283,6 +285,7 @@ const mapStateToProps = ({ pageTemplateListState }) => ({ export default connect(mapStateToProps, { getPageTemplates, + getPageTemplate, archivePageTemplate, unarchivePageTemplate, savePageTemplate, diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/index.js b/src/pages/sponsors-global/page-templates/page-template-popup/index.js index 4514f8908..8fe047d98 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/index.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/index.js @@ -20,13 +20,22 @@ import { FormikProvider, useFormik } from "formik"; import * as yup from "yup"; import MuiFormikTextField from "../../../../components/mui/formik-inputs/mui-formik-textfield"; import PageModules from "./page-template-modules-form"; +import { resetPageTemplateForm } from "../../../../actions/page-template-actions"; import { PAGES_MODULE_KINDS, - PAGE_MODULES_MEDIA_TYPES + PAGE_MODULES_MEDIA_TYPES, + PAGE_MODULES_DOWNLOAD } from "../../../../utils/constants"; -const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { +const PageTemplatePopup = ({ + pageTemplate, + open, + onClose, + onSave, + resetPageTemplateForm +}) => { const handleClose = () => { + resetPageTemplateForm(); onClose(); }; @@ -78,8 +87,16 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { kind: yup.string().equals([PAGES_MODULE_KINDS.DOCUMENT]), name: yup.string().required(T.translate("validation.required")), description: yup.string().required(T.translate("validation.required")), - external_url: yup.string(), - file: yup.array().min(1, T.translate("validation.file_required")) + external_url: yup.string().when("type", { + is: PAGE_MODULES_DOWNLOAD.URL, + then: (schema) => schema.required(T.translate("validation.required")), + otherwise: (schema) => schema.nullable() + }), + file: yup.array().when("type", { + is: PAGE_MODULES_DOWNLOAD.FILE, + then: (schema) => schema.min(1, T.translate("validation.file_required")), + otherwise: (schema) => schema.nullable() + }) }); const mediaModuleSchema = yup.object().shape({ @@ -88,7 +105,7 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { type: yup.string().required(T.translate("validation.required")), upload_deadline: yup.date().required(T.translate("validation.required")), description: yup.string().required(T.translate("validation.required")), - file_type_id: yup.object().when("type", { + file_type_id: yup.number().when("type", { is: PAGE_MODULES_MEDIA_TYPES.FILE, then: (schema) => schema.required(T.translate("validation.required")), otherwise: (schema) => schema.nullable() @@ -124,6 +141,7 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { ...m, custom_order: idx })); + onSave({ ...values, modules: modulesWithOrder }); } }); @@ -148,14 +166,14 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => { > - + - + { /> - + - + - + - + - + @@ -219,8 +237,10 @@ PageTemplatePopup.propTypes = { onSave: PropTypes.func.isRequired }; -const mapStateToProps = ({ currentPageTemplateState }) => ({ - ...currentPageTemplateState +const mapStateToProps = ({ pageTemplateState }) => ({ + pageTemplate: pageTemplateState.entity }); -export default connect(mapStateToProps, {})(PageTemplatePopup); +export default connect(mapStateToProps, { + resetPageTemplateForm +})(PageTemplatePopup); diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-document-download-module.js b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-document-download-module.js index 029a92a19..5241b752b 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-document-download-module.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-document-download-module.js @@ -1,13 +1,29 @@ import React from "react"; import PropTypes from "prop-types"; import T from "i18n-react/dist/i18n-react"; -import { Grid2, InputLabel } from "@mui/material"; - +import { useField } from "formik"; +import { Divider, Grid2, InputLabel } from "@mui/material"; import MuiFormikUpload from "../../../../../components/mui/formik-inputs/mui-formik-upload"; import MuiFormikTextField from "../../../../../components/mui/formik-inputs/mui-formik-textfield"; +import { PAGE_MODULES_DOWNLOAD } from "../../../../../utils/constants"; +import MuiFormikRadioGroup from "../../../../../components/mui/formik-inputs/mui-formik-radio-group"; const DocumentDownloadModule = ({ baseName, index }) => { const buildFieldName = (field) => `${baseName}[${index}].${field}`; + const typeFieldName = buildFieldName("type"); + const [field] = useField(typeFieldName); + const downloadTypeVal = field.value; + + const downloadTypeOptions = [ + { + value: PAGE_MODULES_DOWNLOAD.FILE, + label: T.translate("page_template_list.page_crud.upload_file") + }, + { + value: PAGE_MODULES_DOWNLOAD.URL, + label: T.translate("page_template_list.page_crud.input_url_link") + } + ]; return ( @@ -33,24 +49,39 @@ const DocumentDownloadModule = ({ baseName, index }) => { margin="none" /> - - {T.translate("page_template_list.page_crud.external_url")} - - - + + {downloadTypeVal === PAGE_MODULES_DOWNLOAD.URL && ( + + + {T.translate("page_template_list.page_crud.external_url")} + + + + )} + {downloadTypeVal === PAGE_MODULES_DOWNLOAD.FILE && ( + + + + )} ); }; diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-info-module.js b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-info-module.js index 86a8c0070..175d4e878 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-info-module.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-info-module.js @@ -6,20 +6,16 @@ import { Grid2, Box, InputLabel } from "@mui/material"; import FormikTextEditor from "../../../../../components/inputs/formik-text-editor"; const InfoModule = ({ baseName, index }) => { - const buildFieldName = (field) => `${baseName}[${index}].${field}`; + const name = `${baseName}[${index}].content`; return ( - + {T.translate("page_template_list.page_crud.info_content")} - + diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js index 24269887a..869aba404 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js @@ -1,16 +1,16 @@ import React from "react"; import PropTypes from "prop-types"; import T from "i18n-react/dist/i18n-react"; -import { useFormikContext, getIn } from "formik"; -import { Grid2, Divider, InputLabel } from "@mui/material"; +import { connect } from "react-redux"; +import { getIn, useFormikContext } from "formik"; +import { Divider, Grid2, InputLabel, MenuItem } from "@mui/material"; import MuiFormikTextField from "../../../../../components/mui/formik-inputs/mui-formik-textfield"; import MuiFormikDatepicker from "../../../../../components/mui/formik-inputs/mui-formik-datepicker"; import MuiFormikRadioGroup from "../../../../../components/mui/formik-inputs/mui-formik-radio-group"; import { PAGE_MODULES_MEDIA_TYPES } from "../../../../../utils/constants"; -import MuiFormikAsyncAutocomplete from "../../../../../components/mui/formik-inputs/mui-formik-async-select"; -import { queryMediaFileTypes } from "../../../../../actions/media-file-type-actions"; +import MuiFormikSelect from "../../../../../components/mui/formik-inputs/mui-formik-select"; -const MediaRequestModule = ({ baseName, index }) => { +const MediaRequestModule = ({ baseName, index, mediaFileTypes }) => { const { values } = useFormikContext(); const buildFieldName = (field) => `${baseName}[${index}].${field}`; @@ -28,6 +28,11 @@ const MediaRequestModule = ({ baseName, index }) => { } ]; + const fileTypeOptions = mediaFileTypes.map((ft) => ({ + value: ft.id, + label: `${ft.name} (${ft.allowed_extensions?.join(", ")})` + })); + return ( @@ -78,14 +83,24 @@ const MediaRequestModule = ({ baseName, index }) => { {T.translate("page_template_list.page_crud.allowed_formats")} - ({ - value: item.id, - label: `${item.name} (${item.allowed_extensions?.join(", ")})` - })} - /> + renderValue={(selected) => { + if (!selected || selected === "") { + return {placeholder}; + } + const selectedOption = fileTypeOptions.find( + (t) => t.value === selected + ); + return selectedOption?.label || selected; + }} + > + {fileTypeOptions.map(({ value, label }) => ( + + {label} + + ))} + )} @@ -111,4 +126,8 @@ MediaRequestModule.propTypes = { index: PropTypes.number.isRequired }; -export default MediaRequestModule; +const mapStateToProps = ({ mediaUploadState }) => ({ + mediaFileTypes: mediaUploadState.media_file_types +}); + +export default connect(mapStateToProps, {})(MediaRequestModule); diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/page-template-module-form.test.js b/src/pages/sponsors-global/page-templates/page-template-popup/page-template-module-form.test.js index 2a406b3be..61c6f859d 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/page-template-module-form.test.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/page-template-module-form.test.js @@ -2,6 +2,9 @@ import React from "react"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { Formik, Form, useFormikContext } from "formik"; +import { Provider } from "react-redux"; +import configureStore from "redux-mock-store"; +import thunk from "redux-thunk"; import "@testing-library/jest-dom"; import PageModules from "./page-template-modules-form"; import showConfirmDialog from "../../../../components/mui/showConfirmDialog"; @@ -10,8 +13,13 @@ import { PAGE_MODULES_MEDIA_TYPES } from "../../../../utils/constants"; +const mockStore = configureStore([thunk]); + // Mocks jest.mock("../../../../components/mui/showConfirmDialog", () => jest.fn()); +jest.mock("../../../../actions/media-file-type-actions", () => ({ + getAllMediaFileTypes: jest.fn(() => () => Promise.resolve()) +})); jest.mock( "../../../../components/inputs/formik-text-editor", @@ -88,15 +96,23 @@ jest.mock( } ); -// Helper function to render the component with Formik -const renderWithFormik = (initialValues = { modules: [] }) => - render( - - - - - +// Helper function to render the component with Formik and Redux +const renderWithFormik = (initialValues = { modules: [] }) => { + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); + return render( + + +
+ + +
+
); +}; describe("PageModules", () => { const createModule = (kind, order, id) => ({ @@ -167,10 +183,15 @@ describe("PageModules", () => { renderWithFormik({ modules }); + // INFO module has content field expect( screen.getByTestId("text-editor-modules[0].content") ).toBeInTheDocument(); - expect(screen.getByTestId("upload-modules[1].file")).toBeInTheDocument(); + // DOCUMENT module has name field + expect( + screen.getByTestId("textfield-modules[1].name") + ).toBeInTheDocument(); + // MEDIA module has upload_deadline field expect( screen.getByTestId("datepicker-modules[2].upload_deadline") ).toBeInTheDocument(); @@ -200,12 +221,19 @@ describe("PageModules", () => { createModule(PAGES_MODULE_KINDS.MEDIA, 2, 3) ]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); expect(screen.getByTestId("order-0")).toHaveTextContent("0"); @@ -234,12 +262,19 @@ describe("PageModules", () => { createModule(PAGES_MODULE_KINDS.MEDIA, 2, 3) ]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); expect(screen.getByTestId("module-ids")).toHaveTextContent( @@ -273,12 +308,19 @@ describe("PageModules", () => { createModule(PAGES_MODULE_KINDS.DOCUMENT, 1, 2) ]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); expect(screen.getByTestId("first-module-kind")).toHaveTextContent( @@ -315,12 +357,13 @@ describe("PageModules", () => { const expandIcon = screen.getByTestId("ExpandMoreIcon"); const accordionSummary = expandIcon.closest(".MuiAccordionSummary-root"); + // Initially should be expanded + expect(accordionSummary).toHaveAttribute("aria-expanded", "true"); + await userEvent.click(accordionSummary); await waitFor(() => { - expect( - screen.getByTestId("text-editor-modules[0].content") - ).not.toBeVisible(); + expect(accordionSummary).toHaveAttribute("aria-expanded", "false"); }); }); @@ -361,17 +404,22 @@ describe("PageModules", () => { const firstAccordionSummary = expandIcons[0].closest( ".MuiAccordionSummary-root" ); + const secondAccordionSummary = expandIcons[1].closest( + ".MuiAccordionSummary-root" + ); + + // Both should be expanded initially + expect(firstAccordionSummary).toHaveAttribute("aria-expanded", "true"); + expect(secondAccordionSummary).toHaveAttribute("aria-expanded", "true"); // close first module await userEvent.click(firstAccordionSummary); await waitFor(() => { // first module should be closed - expect( - screen.getByTestId("text-editor-modules[0].content") - ).not.toBeVisible(); - // second module should be expanded - expect(screen.getByTestId("upload-modules[1].file")).toBeVisible(); + expect(firstAccordionSummary).toHaveAttribute("aria-expanded", "false"); + // second module should still be expanded + expect(secondAccordionSummary).toHaveAttribute("aria-expanded", "true"); }); }); }); @@ -383,7 +431,7 @@ describe("PageModules", () => { const modules = [createModule(PAGES_MODULE_KINDS.INFO, 0, 1)]; renderWithFormik({ modules }); - const deleteButton = screen.getByTestId("DeleteIcon").closest("button"); + const deleteButton = screen.getByTestId("delete-module-btn"); await userEvent.click(deleteButton); expect(showConfirmDialog).toHaveBeenCalledWith( @@ -413,12 +461,19 @@ describe("PageModules", () => { createModule(PAGES_MODULE_KINDS.DOCUMENT, 1, 2) ]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); expect(screen.getByTestId("module-count")).toHaveTextContent("2"); @@ -446,12 +501,19 @@ describe("PageModules", () => { const modules = [createModule(PAGES_MODULE_KINDS.INFO, 0, 1)]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); const deleteButton = screen.getByTestId("DeleteIcon").closest("button"); @@ -483,12 +545,19 @@ describe("PageModules", () => { createModule(PAGES_MODULE_KINDS.MEDIA, 2, 3) ]; + const store = mockStore({ + mediaUploadState: { + media_file_types: [] + } + }); render( - -
- - -
+ + +
+ + +
+
); // deletes middle module diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/page-template-modules-form.js b/src/pages/sponsors-global/page-templates/page-template-popup/page-template-modules-form.js index 350a54273..cd5592617 100644 --- a/src/pages/sponsors-global/page-templates/page-template-popup/page-template-modules-form.js +++ b/src/pages/sponsors-global/page-templates/page-template-popup/page-template-modules-form.js @@ -10,6 +10,7 @@ import { IconButton, Typography } from "@mui/material"; +import { connect } from "react-redux"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import DeleteIcon from "@mui/icons-material/Delete"; import UnfoldMoreIcon from "@mui/icons-material/UnfoldMore"; @@ -19,8 +20,9 @@ import { PAGES_MODULE_KINDS } from "../../../../utils/constants"; import InfoModule from "./modules/page-template-info-module"; import DocumentDownloadModule from "./modules/page-template-document-download-module"; import MediaRequestModule from "./modules/page-template-media-request-module"; +import { getAllMediaFileTypes } from "../../../../actions/media-file-type-actions"; -const PageModules = ({ name = "modules" }) => { +const PageModules = ({ name = "modules", getAllMediaFileTypes }) => { const { values, setFieldValue } = useFormikContext(); const modules = getIn(values, name) || []; @@ -35,6 +37,10 @@ const PageModules = ({ name = "modules" }) => { prevModulesLength.current = modules.length; }, [modules.length]); + useEffect(() => { + getAllMediaFileTypes(); + }, []); + const getModuleTitle = (kind) => { switch (kind) { case PAGES_MODULE_KINDS.INFO: @@ -130,6 +136,7 @@ const PageModules = ({ name = "modules" }) => { handleDeleteModule(index, module)} > @@ -173,4 +180,6 @@ PageModules.propTypes = { name: PropTypes.string }; -export default PageModules; +const mapStateToProps = () => ({}); + +export default connect(mapStateToProps, { getAllMediaFileTypes })(PageModules); diff --git a/src/reducers/media_uploads/media-upload-reducer.js b/src/reducers/media_uploads/media-upload-reducer.js index b4a7a4185..9de06ca9b 100644 --- a/src/reducers/media_uploads/media-upload-reducer.js +++ b/src/reducers/media_uploads/media-upload-reducer.js @@ -9,19 +9,17 @@ * 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 { VALIDATE } from "openstack-uicore-foundation/lib/utils/actions"; +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { + MEDIA_UPLOAD_ADDED, RECEIVE_MEDIA_UPLOAD, RESET_MEDIA_UPLOAD_FORM, - UPDATE_MEDIA_UPLOAD, - MEDIA_UPLOAD_ADDED + UPDATE_MEDIA_UPLOAD } from "../../actions/media-upload-actions"; - import { RECEIVE_ALL_MEDIA_FILE_TYPES } from "../../actions/media-file-type-actions"; - -import { VALIDATE } from "openstack-uicore-foundation/lib/utils/actions"; -import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; export const DEFAULT_ENTITY = { @@ -31,7 +29,7 @@ export const DEFAULT_ENTITY = { type_id: 0, max_size: 0, min_uploads_qty: 0, - max_uploads_qty: 0, //0: unrestricted + max_uploads_qty: 0, // 0: unrestricted is_mandatory: false, is_editable: true, private_storage_type: "None", @@ -50,63 +48,43 @@ const DEFAULT_STATE = { const mediaUploadReducer = (state = DEFAULT_STATE, action) => { const { type, payload } = action; switch (type) { - case LOGOUT_USER: - { - // we need this in case the token expired while editing the form - if (payload.hasOwnProperty("persistStore")) { - return state; - } else { - return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} }; - } + case LOGOUT_USER: { + // we need this in case the token expired while editing the form + if (payload?.persistStore) { + return state; } - break; + return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} }; + } case SET_CURRENT_SUMMIT: - case RESET_MEDIA_UPLOAD_FORM: - { - return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} }; - } - break; - case RECEIVE_ALL_MEDIA_FILE_TYPES: - { - let { data } = payload.response; - let media_file_types = data.map((mft) => { - return { - value: mft.id, - label: mft.name - }; - }); - - return { ...state, media_file_types }; - } - break; - case UPDATE_MEDIA_UPLOAD: - { - return { ...state, entity: { ...payload }, errors: {} }; - } - break; + case RESET_MEDIA_UPLOAD_FORM: { + return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} }; + } + case RECEIVE_ALL_MEDIA_FILE_TYPES: { + const { data } = payload.response; + return { ...state, media_file_types: data }; + } + case UPDATE_MEDIA_UPLOAD: { + return { ...state, entity: { ...payload }, errors: {} }; + } case MEDIA_UPLOAD_ADDED: - case RECEIVE_MEDIA_UPLOAD: - { - let entity = { ...payload.response }; + case RECEIVE_MEDIA_UPLOAD: { + const entity = { ...payload.response }; - for (var key in entity) { - if (entity.hasOwnProperty(key)) { - entity[key] = entity[key] == null ? "" : entity[key]; - } + for (const key in entity) { + if (entity.hasOwnProperty(key)) { + entity[key] = entity[key] == null ? "" : entity[key]; } - - return { - ...state, - entity: { ...DEFAULT_ENTITY, ...entity }, - preview: null - }; - } - break; - case VALIDATE: - { - return { ...state, errors: payload.errors }; } - break; + + return { + ...state, + entity: { ...DEFAULT_ENTITY, ...entity }, + preview: null + }; + } + case VALIDATE: { + return { ...state, errors: payload.errors }; + } default: return state; } diff --git a/src/reducers/sponsors_inventory/page-template-list-reducer.js b/src/reducers/sponsors_inventory/page-template-list-reducer.js index 186223903..15a4f0ee1 100644 --- a/src/reducers/sponsors_inventory/page-template-list-reducer.js +++ b/src/reducers/sponsors_inventory/page-template-list-reducer.js @@ -23,7 +23,7 @@ import { PAGES_MODULE_KINDS } from "../../utils/constants"; const DEFAULT_STATE = { pageTemplates: [], - term: null, + term: "", order: "name", orderDir: 1, currentPage: 1, diff --git a/src/reducers/sponsors_inventory/page-template-reducer.js b/src/reducers/sponsors_inventory/page-template-reducer.js new file mode 100644 index 000000000..8acea9cf9 --- /dev/null +++ b/src/reducers/sponsors_inventory/page-template-reducer.js @@ -0,0 +1,109 @@ +/** + * 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 moment from "moment-timezone"; +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { + PAGE_TEMPLATE_ADDED, + PAGE_TEMPLATE_UPDATED, + RECEIVE_PAGE_TEMPLATE, + RESET_PAGE_TEMPLATE_FORM +} from "../../actions/page-template-actions"; +import { + MILLISECONDS_IN_SECOND, + PAGE_MODULES_DOWNLOAD, + PAGES_MODULE_KINDS +} from "../../utils/constants"; + +export const DEFAULT_ENTITY = { + id: 0, + code: "", + name: "", + instructions: "", + items: [], + materials: [], + meta_fields: [] +}; + +const DEFAULT_STATE = { + entity: DEFAULT_ENTITY +}; + +const pageTemplateReducer = (state = DEFAULT_STATE, action) => { + const { type, payload } = action; + switch (type) { + case LOGOUT_USER: { + // we need this in case the token expired while editing the form + if (payload.hasOwnProperty("persistStore")) { + return state; + } + return DEFAULT_STATE; + } + case RESET_PAGE_TEMPLATE_FORM: { + return { ...state, entity: { ...DEFAULT_ENTITY } }; + } + case RECEIVE_PAGE_TEMPLATE: { + const entity = { ...payload.response }; + + entity.modules = entity.modules.map((module) => { + const tmpModule = { + ...module, + ...(module.upload_deadline + ? { + upload_deadline: moment( + module.upload_deadline * MILLISECONDS_IN_SECOND + ) + } + : {}) + }; + + if (module.kind === PAGES_MODULE_KINDS.DOCUMENT) { + if (module.file) { + tmpModule.file = [ + { + bucket: module.file.bucket, + file_name: module.file.file_name, + file_path: module.file.storage_key, + md5: module.file.md5, + mime_type: module.file.mime_type, + public_url: module.file.file_url + } + ]; + tmpModule.type = PAGE_MODULES_DOWNLOAD.FILE; + } else { + tmpModule.type = PAGE_MODULES_DOWNLOAD.URL; + } + } + return tmpModule; + }); + + return { + ...state, + entity + }; + } + case PAGE_TEMPLATE_ADDED: + case PAGE_TEMPLATE_UPDATED: { + return { + ...state, + entity: { + ...DEFAULT_ENTITY + } + }; + } + default: + return state; + } +}; + +export default pageTemplateReducer; diff --git a/src/store.js b/src/store.js index 86973bf8a..236b02084 100644 --- a/src/store.js +++ b/src/store.js @@ -159,6 +159,7 @@ import formTemplateListReducer from "./reducers/sponsors_inventory/form-template import formTemplateItemReducer from "./reducers/sponsors_inventory/form-template-item-reducer.js"; import formTemplateItemListReducer from "./reducers/sponsors_inventory/form-template-item-list-reducer.js"; import pageTemplateListReducer from "./reducers/sponsors_inventory/page-template-list-reducer.js"; +import pageTemplateReducer from "./reducers/sponsors_inventory/page-template-reducer.js"; import sponsorSettingsReducer from "./reducers/sponsor_settings/sponsor-settings-reducer"; import eventRSVPListReducer from "./reducers/rsvps/event-rsvp-list-reducer.js"; import eventRSVPInvitationListReducer from "./reducers/rsvps/event-rsvp-invitation-list-reducer.js"; @@ -332,7 +333,8 @@ const reducers = persistCombineReducers(config, { currentFormTemplateItemState: formTemplateItemReducer, currentFormTemplateItemListState: formTemplateItemListReducer, sponsorSettingsState: sponsorSettingsReducer, - pageTemplateListState: pageTemplateListReducer + pageTemplateListState: pageTemplateListReducer, + pageTemplateState: pageTemplateReducer }); const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; diff --git a/src/utils/constants.js b/src/utils/constants.js index 738fefa11..fa77dd3fe 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -252,11 +252,16 @@ export const PAGE_MODULES_MEDIA_TYPES = { TEXT: "text" }; +export const PAGE_MODULES_DOWNLOAD = { + FILE: "file", + URL: "url" +}; + export const PURCHASE_STATUS = { PENDING: "Pending", PAID: "Paid", CANCELLED: "Cancelled" -} +}; export const SPONSOR_USER_ASSIGNMENT_TYPE = { EXISTING: "existing",