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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ SPONSOR_USERS_API_SCOPES="show-medata/read show-medata/write access-requests/rea
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"
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"
GOOGLE_API_KEY=
ALLOWED_USER_GROUPS="super-admins administrators summit-front-end-administrators summit-room-administrators track-chairs-admins sponsors"
Expand Down
15 changes: 13 additions & 2 deletions src/actions/page-template-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* */

import T from "i18n-react/dist/i18n-react";
import moment from "moment-timezone";
import {
getRequest,
putRequest,
Expand All @@ -27,7 +28,8 @@ import { getAccessTokenSafely } from "../utils/methods";
import {
DEFAULT_CURRENT_PAGE,
DEFAULT_ORDER_DIR,
DEFAULT_PER_PAGE
DEFAULT_PER_PAGE,
PAGES_MODULE_KINDS
} from "../utils/constants";
import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions";

Expand Down Expand Up @@ -143,7 +145,16 @@ export const resetPageTemplateForm = () => (dispatch) => {
const normalizeEntity = (entity) => {
const normalizedEntity = { ...entity };

normalizedEntity.modules = [];
normalizedEntity.modules = entity.modules.map((module) => {
const normalizedModule = { ...module };

if (module.kind === PAGES_MODULE_KINDS.MEDIA && module.upload_deadline) {
normalizedModule.upload_deadline = moment(module.upload_deadline).unix();
}
delete normalizedModule._tempId;

return normalizedModule;
});

return normalizedEntity;
};
Expand Down
134 changes: 134 additions & 0 deletions src/actions/sponsor-pages-actions.js
Original file line number Diff line number Diff line change
@@ -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());
Comment on lines +124 to +125
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

getSponsorForms is not imported - this will cause a runtime error.

The function getSponsorForms() is called but never imported. This will throw a ReferenceError at runtime when cloneGlobalPage succeeds.

Suggested fix

Add the missing import at the top of the file:

+import { getSponsorForms } from "./sponsor-actions";

Or, if the intent was to refresh sponsor pages instead:

       .then(() => {
-        dispatch(getSponsorForms());
+        dispatch(getSponsorPages());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.then(() => {
dispatch(getSponsorForms());
.then(() => {
dispatch(getSponsorPages());
🤖 Prompt for AI Agents
In `@src/actions/sponsor-pages-actions.js` around lines 124 - 125, The call to
getSponsorForms() inside the cloneGlobalPage promise chain will throw because
getSponsorForms is not imported; fix this by adding an import for
getSponsorForms at the top of the file from the module that exports it (the same
module where sponsor form actions live) and keep the dispatch(getSponsorForms())
call, or if the intent was to refresh sponsor pages instead, replace that call
with the correct exported action (e.g., dispatch(getSponsorPages()) )—ensure the
chosen function is imported and referenced (symbols: getSponsorForms,
cloneGlobalPage, dispatch).

dispatch(
snackbarSuccessHandler({
title: T.translate("general.success"),
html: T.translate("sponsor_pages.global_page_popup.success")
})
);
})
.catch(() => {}); // need to catch promise reject
Comment on lines +95 to +133
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing stopLoading() dispatch in cloneGlobalPage.

startLoading() is dispatched at line 101, but stopLoading() is never called. This will leave the loading state active indefinitely after the clone operation completes (success or failure).

Suggested fix
     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
+      .catch(() => {})
+      .finally(() => {
+        dispatch(stopLoading());
+      });
   };
🤖 Prompt for AI Agents
In `@src/actions/sponsor-pages-actions.js` around lines 95 - 133, The
cloneGlobalPage action dispatches startLoading() but never dispatches
stopLoading(), leaving the loading state active; update cloneGlobalPage to
dispatch stopLoading() in both the success path (before/after dispatching
getSponsorForms() and snackbarSuccessHandler) and in the catch/failure path
(before handling errors via snackbarErrorHandler) so that stopLoading() is
always called regardless of postRequest resolving or rejecting; locate the
postRequest(...) promise chain in cloneGlobalPage and add
dispatch(stopLoading()) in both the .then and .catch handlers (or use finally)
to guarantee loading is cleared.

};
5 changes: 5 additions & 0 deletions src/components/menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
6 changes: 4 additions & 2 deletions src/components/mui/formik-inputs/mui-formik-datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ const MuiFormikDatepicker = ({ name, label, required }) => {
label: required ? requiredLabel : label,
error: meta.touched && Boolean(meta.error),
helperText: meta.touched && meta.error,
fullWidth: true,
margin: "normal"
fullWidth: true
},
day: {
sx: {
Expand All @@ -36,6 +35,9 @@ const MuiFormikDatepicker = ({ name, label, required }) => {
}
}
}}
margin="normal"
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
</LocalizationProvider>
);
Expand Down
11 changes: 9 additions & 2 deletions src/components/mui/formik-inputs/mui-formik-radio-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import {
} from "@mui/material";
import { useField } from "formik";

const MuiFormikRadioGroup = ({ name, label, options, ...props }) => {
const MuiFormikRadioGroup = ({
name,
label,
margin = "normal",
options,
...props
}) => {
const [field, meta] = useField({ name });

return (
<FormControl
fullWidth
margin="normal"
margin={margin}
error={meta.touched && Boolean(meta.error)}
>
{label && <FormLabel id="radio-group-label">{label}</FormLabel>}
Expand Down Expand Up @@ -56,6 +62,7 @@ const MuiFormikRadioGroup = ({ name, label, options, ...props }) => {
MuiFormikRadioGroup.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string,
margin: PropTypes.string,
options: PropTypes.array.isRequired
};

Expand Down
15 changes: 14 additions & 1 deletion src/components/mui/formik-inputs/mui-formik-select.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
FormHelperText,
FormControl,
InputAdornment,
IconButton
IconButton,
InputLabel
} from "@mui/material";
import ClearIcon from "@mui/icons-material/Clear";
import { useField } from "formik";

const MuiFormikSelect = ({
name,
label,
placeholder,
children,
isClearable,
Expand All @@ -24,12 +26,23 @@ const MuiFormikSelect = ({
helpers.setValue("");
};

const hasValue =
field.value !== "" && field.value !== undefined && field.value !== null;

return (
<FormControl fullWidth error={meta.touched && Boolean(meta.error)}>
{label && (
<InputLabel htmlFor={name} id={`${name}-label`} shrink={hasValue}>
{label}
</InputLabel>
)}
<Select
name={name}
// eslint-disable-next-line react/jsx-props-no-spreading
{...field}
labelId={`${name}-label`}
label={label}
notched={hasValue}
Comment on lines +34 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/mui/formik-inputs/mui-formik-select.js

Repository: fntechgit/summit-admin

Length of output: 2702


🏁 Script executed:

rg "MuiFormikSelect" --type js --type jsx -A 3 -B 1 | head -100

Repository: fntechgit/summit-admin

Length of output: 93


🏁 Script executed:

rg "MuiFormikSelect" -A 3 -B 1 | head -150

Repository: fntechgit/summit-admin

Length of output: 12188


🏁 Script executed:

rg "MuiFormikSelect" -B 5 -A 8 "src/components/mui/formik-inputs/additional-input/additional-input.js"

Repository: fntechgit/summit-admin

Length of output: 1516


Label and placeholder text may visually overlap when no value is selected.

When displayEmpty is true and a placeholder is provided (currently used in src/components/mui/formik-inputs/additional-input/additional-input.js), the InputLabel will remain un-shrunk (inside the select box) while the placeholder text displays in the same area. This causes visual overlap.

The label should always shrink when a placeholder is present:

Suggested fix
  const hasValue =
    field.value !== "" && field.value !== undefined && field.value !== null;
+ const shouldShrink = hasValue || Boolean(placeholder);

  return (
    <FormControl fullWidth error={meta.touched && Boolean(meta.error)}>
      {label && (
-        <InputLabel htmlFor={name} id={`${name}-label`} shrink={hasValue}>
+        <InputLabel htmlFor={name} id={`${name}-label`} shrink={shouldShrink}>
          {label}
        </InputLabel>
      )}
      <Select
        name={name}
        // eslint-disable-next-line react/jsx-props-no-spreading
        {...field}
        labelId={`${name}-label`}
        label={label}
-        notched={hasValue}
+        notched={shouldShrink}
🤖 Prompt for AI Agents
In `@src/components/mui/formik-inputs/mui-formik-select.js` around lines 34 - 45,
The label can overlap the placeholder because hasValue only reflects a selected
value; update the hasValue logic in the component that renders InputLabel and
Select so it treats a provided placeholder (and displayEmpty=true) as a value —
e.g., compute hasValue = Boolean(field.value) || (displayEmpty &&
Boolean(placeholder)) — then continue using hasValue for InputLabel's shrink and
Select's notched props (symbols: InputLabel, Select, hasValue, placeholder,
displayEmpty, field).

displayEmpty
renderValue={(selected) => {
if (!selected || selected === "") {
Expand Down
Loading