diff --git a/.env.example b/.env.example index 89c72cad4..d1a92df06 100644 --- a/.env.example +++ b/.env.example @@ -12,13 +12,15 @@ PRINT_APP_URL=https://badge-print-app.dev.fnopen.com PUB_API_BASE_URL= OS_BASE_URL= SCOPES_BASE_REALM=${API_BASE_URL} +PURCHASES_API_URL=https://purchases-api.dev.fnopen.com PURCHASES_API_SCOPES="purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write customized-form/write customized-form/read carts/read carts/write purchases/read" -SPONSOR_USERS_API_SCOPES="show-medata/read show-medata/write access-requests/read access-requests/write sponsor-users/read sponsor-users/write groups/read groups/write" +SPONSOR_USERS_API_URL=https://sponsor-users-api.dev.fnopen.com +SPONSOR_USERS_SCOPES="show-medata/read show-medata/write access-requests/read access-requests/write sponsor-users/read sponsor-users/write groups/read groups/write media-upload/write" EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" SPONSOR_PAGES_API_URL=https://sponsor-pages-api.dev.fnopen.com -SPONSOR_PAGES_SCOPES="page-template/read page-template/write show-page/read show-page/write" -SCOPES="profile openid offline_access ${SPONSOR_USERS_API_SCOPES} ${PURCHASES_API_SCOPES} ${EMAIL_SCOPES} ${FILE_UPLOAD_SCOPES} ${SPONSOR_PAGES_SCOPES} ${SCOPES_BASE_REALM}/summits/delete-event ${SCOPES_BASE_REALM}/summits/write ${SCOPES_BASE_REALM}/summits/write-event ${SCOPES_BASE_REALM}/summits/read/all ${SCOPES_BASE_REALM}/summits/read ${SCOPES_BASE_REALM}/summits/publish-event ${SCOPES_BASE_REALM}/members/read ${SCOPES_BASE_REALM}/members/read/me ${SCOPES_BASE_REALM}/speakers/write ${SCOPES_BASE_REALM}/attendees/write ${SCOPES_BASE_REALM}/members/write ${SCOPES_BASE_REALM}/organizations/write ${SCOPES_BASE_REALM}/organizations/read ${SCOPES_BASE_REALM}/summits/write-presentation-materials ${SCOPES_BASE_REALM}/summits/registration-orders/update ${SCOPES_BASE_REALM}/summits/registration-orders/delete ${SCOPES_BASE_REALM}/summits/registration-orders/create/offline ${SCOPES_BASE_REALM}/summits/badge-scans/read entity-updates/publish ${SCOPES_BASE_REALM}/audit-logs/read" +SPONSOR_PAGES_SCOPES="page-template/read page-template/write show-page/read show-page/write media-upload/read" +SCOPES="profile openid offline_access reports/all ${EMAIL_SCOPES} ${INVENTORY_API_SCOPES} ${FILE_UPLOAD_SCOPES} ${PURCHASES_API_SCOPES} ${SPONSOR_USERS_SCOPES} ${SPONSOR_PAGES_SCOPES} ${SCOPES_BASE_REALM}/summits/delete-event ${SCOPES_BASE_REALM}/companies/read ${SCOPES_BASE_REALM}/companies/write ${SCOPES_BASE_REALM}/summits/write ${SCOPES_BASE_REALM}/summits/write-event ${SCOPES_BASE_REALM}/summits/read/all ${SCOPES_BASE_REALM}/summits/read ${SCOPES_BASE_REALM}/summits/publish-event ${SCOPES_BASE_REALM}/members/read ${SCOPES_BASE_REALM}/members/read/me ${SCOPES_BASE_REALM}/speakers/write ${SCOPES_BASE_REALM}/attendees/write ${SCOPES_BASE_REALM}/members/write ${SCOPES_BASE_REALM}/organizations/write ${SCOPES_BASE_REALM}/organizations/read ${SCOPES_BASE_REALM}/summits/write-presentation-materials ${SCOPES_BASE_REALM}/summits/registration-orders/update ${SCOPES_BASE_REALM}/summits/registration-orders/delete ${SCOPES_BASE_REALM}/summits/registration-orders/create/offline ${SCOPES_BASE_REALM}/summits/badge-scans/read config-values/write ${SCOPES_BASE_REALM}/summit-administrator-groups/read ${SCOPES_BASE_REALM}/summit-administrator-groups/write ${SCOPES_BASE_REALM}/summit-media-file-types/read ${SCOPES_BASE_REALM}/summit-media-file-types/write user-roles/write entity-updates/publish ${SCOPES_BASE_REALM}/audit-logs/read filter-criteria/read filter-criteria/write" GOOGLE_API_KEY= ALLOWED_USER_GROUPS="super-admins administrators summit-front-end-administrators summit-room-administrators track-chairs-admins sponsors" APP_CLIENT_NAME="openstack" @@ -35,7 +37,5 @@ SENTRY_PROJECT= SENTRY_TRACE_SAMPLE_RATE= SENTRY_TRACE_PROPAGATION_TARGETS= CFP_APP_BASE_URL= -PURCHASES_API_URL= -SPONSOR_USERS_API_URL= S3_MEDIA_UPLOADS_ENDPOINT_URL=https://fntech.sfo2.digitaloceanspaces.com S3_MEDIA_UPLOADS_BUCKET_NAME=PresentationMediaUploads_DEV \ No newline at end of file diff --git a/src/actions/sponsor-mu-actions.js b/src/actions/sponsor-mu-actions.js new file mode 100644 index 000000000..6ce634e3c --- /dev/null +++ b/src/actions/sponsor-mu-actions.js @@ -0,0 +1,176 @@ +/** + * 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 { + createAction, + getRequest, + putRequest, + deleteRequest, + startLoading, + stopLoading +} from "openstack-uicore-foundation/lib/utils/actions"; +import { getAccessTokenSafely } from "../utils/methods"; +import { + DEFAULT_CURRENT_PAGE, + DEFAULT_ORDER_DIR, + DEFAULT_PER_PAGE +} from "../utils/constants"; +import { snackbarErrorHandler } from "./base-actions"; + +export const REQUEST_SPONSOR_MEDIA_UPLOADS = "REQUEST_SPONSOR_MEDIA_UPLOADS"; +export const RECEIVE_SPONSOR_MEDIA_UPLOADS = "RECEIVE_SPONSOR_MEDIA_UPLOADS"; +export const REQUEST_GENERAL_MEDIA_UPLOADS = "REQUEST_GENERAL_MEDIA_UPLOADS"; +export const RECEIVE_GENERAL_MEDIA_UPLOADS = "RECEIVE_GENERAL_MEDIA_UPLOADS"; +export const SPONSOR_MEDIA_UPLOAD_FILE_UPLOADED = + "SPONSOR_MEDIA_UPLOAD_FILE_UPLOADED"; +export const SPONSOR_MEDIA_UPLOAD_FILE_DELETED = + "SPONSOR_MEDIA_UPLOAD_FILE_DELETED"; + +export const getSponsorMURequests = + ( + currentPage = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR + ) => + async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const summitTZ = currentSummit.time_zone.name; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + page: currentPage, + // fields: "id,name,max_file_size,media_upload,file_type", + // relations: "media_upload,file_type", + per_page: perPage, + access_token: accessToken + }; + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "" : "-"; + params.order = `${orderDirSign}${order}`; + } + + return getRequest( + createAction(REQUEST_SPONSOR_MEDIA_UPLOADS), + createAction(RECEIVE_SPONSOR_MEDIA_UPLOADS), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/custom-media-request-modules`, + snackbarErrorHandler, + { order, orderDir, currentPage, perPage, summitTZ } + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; + +export const getGeneralMURequests = + ( + currentPage = DEFAULT_CURRENT_PAGE, + perPage = DEFAULT_PER_PAGE, + order = "id", + orderDir = DEFAULT_ORDER_DIR + ) => + async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const summitTZ = currentSummit.time_zone.name; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + page: currentPage, + // fields: "id,name,max_file_size,media_upload,file_type", + expand: "media_upload,file_type", + per_page: perPage, + access_token: accessToken + }; + + // order + if (order != null && orderDir != null) { + const orderDirSign = orderDir === 1 ? "" : "-"; + params.order = `${orderDirSign}${order}`; + } + + return getRequest( + createAction(REQUEST_GENERAL_MEDIA_UPLOADS), + createAction(RECEIVE_GENERAL_MEDIA_UPLOADS), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/managed-media-request-modules`, + snackbarErrorHandler, + { order, orderDir, currentPage, perPage, summitTZ } + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; + +export const uploadFileForSponsorMU = + (pageId, moduleId, fileObj) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return putRequest( + null, + createAction("DUMMY_ACTION"), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/available-pages/${pageId}/modules/${moduleId}/file`, + fileObj, + snackbarErrorHandler + )(params)(dispatch) + .then(({ response }) => { + dispatch( + createAction(SPONSOR_MEDIA_UPLOAD_FILE_UPLOADED)({ + ...response, + moduleId + }) + ); + }) + .finally(() => { + dispatch(stopLoading()); + }); + }; + +export const removeFileForSponsorMU = + (pageId, moduleId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return deleteRequest( + null, + createAction(SPONSOR_MEDIA_UPLOAD_FILE_DELETED)({ moduleId }), + `${window.SPONSOR_PAGES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/available-pages/${pageId}/modules/${moduleId}/file`, + null, + snackbarErrorHandler + )(params)(dispatch).finally(() => { + dispatch(stopLoading()); + }); + }; diff --git a/src/components/upload-dialog/index.js b/src/components/upload-dialog/index.js new file mode 100644 index 000000000..90928e132 --- /dev/null +++ b/src/components/upload-dialog/index.js @@ -0,0 +1,176 @@ +/** + * 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, { useState } from "react"; +import { + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + Divider, + IconButton, + Typography +} from "@mui/material"; +import PropTypes from "prop-types"; +import UploadInputV2 from "openstack-uicore-foundation/lib/components/inputs/upload-input-v2"; +import T from "i18n-react/dist/i18n-react"; +import CloseIcon from "@mui/icons-material/Close"; +import NoteAddIcon from "@mui/icons-material/NoteAdd"; +import DeleteIcon from "@mui/icons-material/Delete"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import DialogActions from "@mui/material/DialogActions"; + +const MAX_PAGE_MODULE_UPLOAD_QTY = 1; + +const CurrentFile = ({ file, onRemove }) => ( + + + + + + + {file.filename} + + + {file.size} {T.translate("upload_input.complete")} + + + + + + + + + +); + +const UploadDialog = ({ + name, + value, + open, + fileMeta, + maxFiles = MAX_PAGE_MODULE_UPLOAD_QTY, + onClose, + onUpload, + onRemove +}) => { + const [uploadedFile, setUploadedFile] = useState(null); + + const mediaType = { + id: name, + max_size: fileMeta.max_file_size, + max_uploads_qty: maxFiles, + type: { + allowed_extensions: fileMeta?.allowed_extensions?.split(",") || [] + } + }; + + const handleUpload = () => { + onUpload(uploadedFile); + }; + + const handleRemove = () => { + if (onRemove) { + onRemove(); + } + }; + + const handleClose = () => { + setUploadedFile(null); + onClose(); + }; + + const canAddMore = () => (value?.length || 0) < maxFiles; + + return ( + + + {T.translate("edit_sponsor.mu_tab.upload_input.upload_file")} + + ({ + position: "absolute", + right: 8, + top: 8, + color: theme.palette.grey[500] + })} + > + + + + + + {fileMeta.name} + + + {fileMeta.description} + + {value ? ( + <> + + + + ) : ( + setUploadedFile(null)} + postUrl={`${window.FILE_UPLOAD_API_BASE_URL}/api/v1/files/upload`} + djsConfig={{ withCredentials: true }} + maxFiles={maxFiles} + canAdd={canAddMore()} + parallelChunkUploads + /> + )} + + + + + + ); +}; + +UploadDialog.propTypes = { + open: PropTypes.bool.isRequired, + name: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired +}; + +export default UploadDialog; diff --git a/src/i18n/en.json b/src/i18n/en.json index 9db755450..b8c8735f4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2501,6 +2501,21 @@ "details": "Details", "purchases": "purchases" }, + "mu_tab": { + "alert_info": "Here you can see the status of this Sponsor's Media Uploads. If an additional file upload is required, it must be requested from the specific page where it is needed.", + "media_upload": "media upload", + "sponsor_request": "Sponsor Specific Request", + "general_request": "General Media Request", + "add_on": "Add-on", + "max_size": "Max Size", + "format": "Format", + "deadline": "Deadline", + "status": "Status", + "upload_input": { + "complete": "Complete", + "upload_file": "Upload file" + } + }, "placeholders": { "select_sponsorship": "Select a Sponsorship", "sponsorship_type": "Start typing to choose a Tier...", diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index 417907aac..782a07f6e 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -47,6 +47,7 @@ import SponsorPagesTab from "./sponsor-pages-tab"; import SponsorFormsManageItems from "./sponsor-forms-tab/components/manage-items/sponsor-forms-manage-items"; import { SPONSOR_TABS } from "../../utils/constants"; import SponsorPurchasesTab from "./sponsor-purchases-tab"; +import SponsorMediaUploadTab from "./sponsor-media-upload-tab"; export const tabsToFragmentMap = [ "general", @@ -256,6 +257,9 @@ const EditSponsorPage = (props) => { history={history} /> + + + {isNestedFormItemRoute ? ( diff --git a/src/pages/sponsors/sponsor-media-upload-tab/index.js b/src/pages/sponsors/sponsor-media-upload-tab/index.js new file mode 100644 index 000000000..db596e786 --- /dev/null +++ b/src/pages/sponsors/sponsor-media-upload-tab/index.js @@ -0,0 +1,283 @@ +/** + * 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 { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { Box, Chip, IconButton } from "@mui/material"; +import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; +import DownloadIcon from "@mui/icons-material/Download"; +import DeleteIcon from "@mui/icons-material/Delete"; +import VisibilityIcon from "@mui/icons-material/Visibility"; +import { + getGeneralMURequests, + getSponsorMURequests, + removeFileForSponsorMU, + uploadFileForSponsorMU +} from "../../../actions/sponsor-mu-actions"; +import CustomAlert from "../../../components/mui/custom-alert"; +import MuiTable from "../../../components/mui/table/mui-table"; +import { SPONSOR_MEDIA_UPLOAD_STATUS } from "../../../utils/constants"; +import UploadDialog from "../../../components/upload-dialog"; +import showConfirmDialog from "../../../components/mui/showConfirmDialog"; + +const SponsorMediaUploadTab = ({ + sponsorRequests, + generalRequests, + getSponsorMURequests, + getGeneralMURequests, + uploadFileForSponsorMU, + removeFileForSponsorMU +}) => { + const [uploadModule, setUploadModule] = useState(null); + + useEffect(() => { + getSponsorMURequests(); + getGeneralMURequests(); + }, []); + + const handleSponsorPageChange = (page) => { + const { perPage, order, orderDir } = sponsorRequests; + getSponsorMURequests(page, perPage, order, orderDir); + }; + + const handleSponsorSort = (key, dir) => { + const { currentPage, perPage } = sponsorRequests; + getSponsorMURequests(currentPage, perPage, key, dir); + }; + + const handleUpload = (item) => { + setUploadModule(item); + }; + + const handleUploadFile = (file) => { + uploadFileForSponsorMU(uploadModule.page_id, uploadModule.id, file).then( + () => { + setUploadModule(null); + } + ); + }; + + const handleView = (item) => { + console.log("VIEW : ", item); + }; + + const handleDownload = (item) => { + console.log("DOWNLOAD : ", item); + }; + + const handleDelete = async (item) => { + const isConfirmed = await showConfirmDialog({ + title: T.translate("general.are_you_sure"), + text: `${T.translate("general.row_remove_warning")} ${item.name}`, + type: "warning", + showCancelButton: true, + confirmButtonColor: "#DD6B55", + confirmButtonText: T.translate("general.yes_delete") + }); + + if (isConfirmed) { + removeFileForSponsorMU(item.page_id, item.id); + } + }; + + const handleGeneralPageChange = (page) => { + const { perPage, order, orderDir } = generalRequests; + getGeneralMURequests(page, perPage, order, orderDir); + }; + + const handleGeneralSort = (key, dir) => { + const { currentPage, perPage } = generalRequests; + getGeneralMURequests(currentPage, perPage, key, dir); + }; + + const getTableColumns = (type) => { + const isSponsor = type === "sponsor"; + const nameLabel = isSponsor + ? T.translate("edit_sponsor.mu_tab.sponsor_request") + : T.translate("edit_sponsor.mu_tab.general_request"); + + const getChipColor = (status) => { + switch (status) { + case SPONSOR_MEDIA_UPLOAD_STATUS.DEADLINE_ALERT: + return "warning"; + case SPONSOR_MEDIA_UPLOAD_STATUS.DEADLINE_MISSED: + return "error"; + case SPONSOR_MEDIA_UPLOAD_STATUS.COMPLETE: + return "success"; + default: + return "default"; + } + }; + + return [ + { + columnKey: "name", + header: nameLabel, + sortable: true + }, + { + columnKey: "add_on", + header: T.translate("edit_sponsor.mu_tab.add_on") + }, + { + columnKey: "max_size", + header: T.translate("edit_sponsor.mu_tab.max_size") + }, + { + columnKey: "format", + header: T.translate("edit_sponsor.mu_tab.format") + }, + { + columnKey: "deadline", + header: T.translate("edit_sponsor.mu_tab.deadline"), + sortable: true + }, + { + columnKey: "status", + header: T.translate("edit_sponsor.mu_tab.status"), + sortable: true, + render: (row) => ( + + ) + }, + { + columnKey: "view", + header: "", + width: 80, + align: "center", + render: (row) => ( + handleView(row)} + > + + + ) + }, + { + columnKey: "download", + header: "", + width: 80, + align: "center", + render: (row) => ( + handleDownload(row)} + > + + + ) + }, + { + columnKey: "upload_delete", + header: "", + width: 80, + align: "center", + render: (row) => { + if (row.media_upload) { + return ( + handleDelete(row)}> + + + ); + } + return ( + handleUpload(row)}> + + + ); + } + } + ]; + }; + + return ( + + +
+ + {sponsorRequests.totalCount}{" "} + {T.translate("edit_sponsor.mu_tab.media_upload")} + {sponsorRequests.totalCount === 1 ? "" : "s"} + + +
+
+ + {generalRequests.totalCount}{" "} + {T.translate("edit_sponsor.mu_tab.media_upload")} + {generalRequests.totalCount === 1 ? "" : "s"} + + +
+ setUploadModule(null)} + onUpload={handleUploadFile} + onRemove={() => handleDelete(uploadModule)} + value={uploadModule?.media_upload} + fileMeta={{ + ...(uploadModule?.file_type || {}), + max_file_size: uploadModule?.max_file_size + }} + maxFiles={1} + /> +
+ ); +}; + +const mapStateToProps = ({ sponsorPageMUListState }) => ({ + ...sponsorPageMUListState +}); + +export default connect(mapStateToProps, { + getSponsorMURequests, + getGeneralMURequests, + uploadFileForSponsorMU, + removeFileForSponsorMU +})(SponsorMediaUploadTab); diff --git a/src/reducers/sponsors/sponsor-page-mu-list-reducer.js b/src/reducers/sponsors/sponsor-page-mu-list-reducer.js new file mode 100644 index 000000000..046a2785c --- /dev/null +++ b/src/reducers/sponsors/sponsor-page-mu-list-reducer.js @@ -0,0 +1,238 @@ +/** + * 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 moment from "moment-timezone"; +import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; +import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { + RECEIVE_GENERAL_MEDIA_UPLOADS, + RECEIVE_SPONSOR_MEDIA_UPLOADS, + REQUEST_GENERAL_MEDIA_UPLOADS, + REQUEST_SPONSOR_MEDIA_UPLOADS, + SPONSOR_MEDIA_UPLOAD_FILE_DELETED, + SPONSOR_MEDIA_UPLOAD_FILE_UPLOADED +} from "../../actions/sponsor-mu-actions"; +import { bytesToMb } from "../../utils/methods"; +import { + DEADLINE_ALERT_DAYS, + SPONSOR_MEDIA_UPLOAD_STATUS +} from "../../utils/constants"; + +const DEFAULT_STATE = { + sponsorRequests: { + requests: [], + order: "name", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalCount: 0 + }, + generalRequests: { + requests: [], + order: "name", + orderDir: 1, + currentPage: 1, + lastPage: 1, + perPage: 10, + totalCount: 0 + }, + summitTZ: "" +}; + +const getStatus = (mediaObject) => { + let status = SPONSOR_MEDIA_UPLOAD_STATUS.COMPLETE; + if (!mediaObject.media_upload) { + if (mediaObject.upload_deadline < moment().unix()) { + status = SPONSOR_MEDIA_UPLOAD_STATUS.DEADLINE_MISSED; + } else if ( + mediaObject.upload_deadline < + moment().add(DEADLINE_ALERT_DAYS, "days").unix() + ) { + status = SPONSOR_MEDIA_UPLOAD_STATUS.DEADLINE_ALERT; + } else { + status = SPONSOR_MEDIA_UPLOAD_STATUS.PENDING; + } + } + + return status; +}; + +const mapMediaObject = (mediaObject, summitTZ) => { + const deadline = mediaObject.upload_deadline + ? epochToMomentTimeZone(mediaObject.upload_deadline, summitTZ)?.format( + "YYYY/MM/DD" + ) + : "N/A"; + + return { + ...mediaObject, + add_on: mediaObject.add_ons?.map((a) => a.name).join(", ") || "N/A", + max_size: `${bytesToMb(mediaObject.max_file_size)} MB`, + format: mediaObject.file_type?.allowed_extensions || "N/A", + deadline, + status: getStatus(mediaObject) + }; +}; + +const sponsorPageMUListReducer = (state = DEFAULT_STATE, action) => { + const { type, payload } = action; + + switch (type) { + case SET_CURRENT_SUMMIT: + case LOGOUT_USER: { + return DEFAULT_STATE; + } + case REQUEST_SPONSOR_MEDIA_UPLOADS: { + const { order, orderDir, currentPage, perPage, summitTZ } = payload; + + return { + ...state, + sponsorRequests: { + ...state.sponsorRequests, + order, + orderDir, + requests: [], + currentPage, + perPage + }, + summitTZ + }; + } + case RECEIVE_SPONSOR_MEDIA_UPLOADS: { + const { + current_page: currentPage, + total, + last_page: lastPage + } = payload.response; + + const requests = payload.response.data.map((a) => + mapMediaObject(a, state.summitTZ) + ); + + return { + ...state, + sponsorRequests: { + ...state.sponsorRequests, + requests, + currentPage, + totalCount: total, + lastPage + } + }; + } + case REQUEST_GENERAL_MEDIA_UPLOADS: { + const { order, orderDir, currentPage, perPage, summitTZ } = payload; + + return { + ...state, + generalRequests: { + ...state.generalRequests, + order, + orderDir, + requests: [], + currentPage, + perPage + }, + summitTZ + }; + } + case RECEIVE_GENERAL_MEDIA_UPLOADS: { + const { + current_page: currentPage, + total, + last_page: lastPage + } = payload.response; + + const requests = payload.response.data.map((a) => + mapMediaObject(a, state.summitTZ) + ); + + return { + ...state, + generalRequests: { + ...state.generalRequests, + requests, + currentPage, + totalCount: total, + lastPage + } + }; + } + case SPONSOR_MEDIA_UPLOAD_FILE_UPLOADED: { + const { moduleId, ...file } = payload; + return { + ...state, + sponsorRequests: { + ...state.sponsorRequests, + requests: state.sponsorRequests.requests.map((r) => + r.id === moduleId + ? { + ...r, + media_upload: file, + status: getStatus({ ...r, media_upload: true }) + } + : r + ) + }, + generalRequests: { + ...state.generalRequests, + requests: state.generalRequests.requests.map((r) => + r.id === moduleId + ? { + ...r, + media_upload: file, + status: getStatus({ ...r, media_upload: true }) + } + : r + ) + } + }; + } + case SPONSOR_MEDIA_UPLOAD_FILE_DELETED: { + const { moduleId } = payload; + return { + ...state, + sponsorRequests: { + ...state.sponsorRequests, + requests: state.sponsorRequests.requests.map((r) => + r.id === moduleId + ? { + ...r, + media_upload: null, + status: getStatus({ ...r, media_upload: false }) + } + : r + ) + }, + generalRequests: { + ...state.generalRequests, + requests: state.generalRequests.requests.map((r) => + r.id === moduleId + ? { + ...r, + media_upload: null, + status: getStatus({ ...r, media_upload: false }) + } + : r + ) + } + }; + } + default: + return state; + } +}; + +export default sponsorPageMUListReducer; diff --git a/src/store.js b/src/store.js index 05b12a62f..5d92e92ec 100644 --- a/src/store.js +++ b/src/store.js @@ -170,6 +170,7 @@ import sponsorCustomizedFormItemsListReducer from "./reducers/sponsors/sponsor-c import showPagesListReducer from "./reducers/sponsors/show-pages-list-reducer.js"; import sponsorPagePurchaseListReducer from "./reducers/sponsors/sponsor-page-purchase-list-reducer.js"; import sponsorPagePagesListReducer from "./reducers/sponsors/sponsor-page-pages-list-reducer.js"; +import sponsorPageMUListReducer from "./reducers/sponsors/sponsor-page-mu-list-reducer.js"; // default: localStorage if web, AsyncStorage if react-native @@ -258,6 +259,7 @@ const reducers = persistCombineReducers(config, { sponsorPageFormsListState: sponsorPageFormsListReducer, sponsorPageCartListState: sponsorPageCartListReducer, sponsorPagePagesListState: sponsorPagePagesListReducer, + sponsorPageMUListState: sponsorPageMUListReducer, sponsorCustomizedFormState: sponsorCustomizedFormReducer, sponsorCustomizedFormItemsListState: sponsorCustomizedFormItemsListReducer, sponsorPagePurchaseListState: sponsorPagePurchaseListReducer, diff --git a/src/utils/constants.js b/src/utils/constants.js index 3983fbeb5..7a7e7e0f4 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -264,3 +264,15 @@ export const SPONSOR_USER_ASSIGNMENT_TYPE = { EXISTING: "existing", NEW: "new" }; + +export const SPONSOR_MEDIA_UPLOAD_STATUS = { + PENDING: "PENDING", + DEADLINE_ALERT: "DEADLINE ALERT", + DEADLINE_MISSED: "DEADLINE MISSED", + COMPLETE: "COMPLETE" +}; + +// eslint-disable-next-line no-magic-numbers +export const BYTES_IN_MEGABYTE = 1024 * 1024; + +export const DEADLINE_ALERT_DAYS = 3; diff --git a/src/utils/methods.js b/src/utils/methods.js index a558014ca..a73060818 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -22,6 +22,7 @@ import * as Sentry from "@sentry/react"; import T from "i18n-react/dist/i18n-react"; import { BADGE_QR_MINIMUM_EXPECTED_FIELDS, + BYTES_IN_MEGABYTE, ERROR_CODE_401, ERROR_CODE_403, ERROR_CODE_412, @@ -530,3 +531,6 @@ export const formatBadgeQR = (code, summit) => { return null; }; + +// eslint-disable-next-line no-magic-numbers +export const bytesToMb = (bytes) => (bytes / BYTES_IN_MEGABYTE).toFixed(2);