Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
176 changes: 176 additions & 0 deletions src/actions/sponsor-mu-actions.js
Original file line number Diff line number Diff line change
@@ -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());
});
};
180 changes: 180 additions & 0 deletions src/components/upload-dialog/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* 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 }) => (
<Box sx={{ display: "flex", flexDirection: "row" }}>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 40
}}
>
<NoteAddIcon sx={{ mr: 1 }} color="primary" />
</Box>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="body2" sx={{ mb: 1 }}>
{file.filename}
</Typography>
<Typography variant="body2" sx={{ color: "text.secondary" }}>
{file.size} {T.translate("upload_input.complete")}
</Typography>
</Box>
<IconButton aria-label="delete" onClick={onRemove}>
<DeleteIcon />
</IconButton>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 40
}}
>
<CheckCircleIcon color="success" sx={{ ml: 1 }} />
</Box>
</Box>
);

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 canAddMore = () => (value?.length || 0) < maxFiles;

const getInputValue = () =>
value?.length > 0
? value.map((file) => ({
...file,
filename:
file.file_name ?? file.filename ?? file.file_path ?? file.file_url
}))
: [];

return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>
{T.translate("edit_sponsor.mu_tab.upload_input.upload_file")}
</DialogTitle>
<IconButton
aria-label="close"
onClick={onClose}
sx={(theme) => ({
position: "absolute",
right: 8,
top: 8,
color: theme.palette.grey[500]
})}
>
<CloseIcon />
</IconButton>
<Divider />
<DialogContent>
<Typography variant="body1" sx={{ mb: 2 }}>
{fileMeta.name}
</Typography>
<Typography variant="body2" sx={{ mb: 2, color: "text.secondary" }}>
{fileMeta.description}
</Typography>
{value ? (
<>
<Divider sx={{ marginLeft: -2, marginRight: -2, mb: 2 }} />
<CurrentFile file={value} onRemove={handleRemove} />
</>
) : (
<UploadInputV2
id={`media_upload_${name}`}
name={name}
onUploadComplete={setUploadedFile}
value={getInputValue()}
mediaType={mediaType}
onRemove={() => setUploadedFile(null)}
postUrl={`${window.FILE_UPLOAD_API_BASE_URL}/api/v1/files/upload`}
djsConfig={{ withCredentials: true }}
maxFiles={maxFiles}
canAdd={canAddMore()}
parallelChunkUploads
/>
)}
</DialogContent>
<DialogActions>
<Button
onClick={handleUpload}
fullWidth
disabled={!uploadedFile}
variant="contained"
>
{T.translate("edit_sponsor.mu_tab.upload_input.upload_file")}
</Button>
</DialogActions>
</Dialog>
);
};

UploadDialog.propTypes = {
open: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired
};

export default UploadDialog;
Loading