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
22 changes: 20 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,23 @@ 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
.utc(module.upload_deadline)
.unix();
}

if (module.kind === PAGES_MODULE_KINDS.DOCUMENT && module.file) {
normalizedModule.file = module.file[0] || null;
}

delete normalizedModule._tempId;

return normalizedModule;
});

return normalizedEntity;
};
Expand Down
135 changes: 135 additions & 0 deletions src/actions/sponsor-pages-actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* 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 { getSponsorForms } from "./sponsor-forms-actions";
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());
});
Comment on lines +85 to +93
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

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the actual file and lines mentioned
cat -n src/actions/sponsor-pages-actions.js | sed -n '80,100p'

Repository: fntechgit/summit-admin

Length of output: 975


🏁 Script executed:

# Find where getRequest is defined
rg "const getRequest|function getRequest|export.*getRequest" --type js -B 2 -A 10

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check imports in the file to see where getRequest comes from
head -30 src/actions/sponsor-pages-actions.js

Repository: fntechgit/summit-admin

Length of output: 1180


🏁 Script executed:

# Find cloneGlobalPage implementation
sed -n '96,150p' src/actions/sponsor-pages-actions.js

Repository: fntechgit/summit-admin

Length of output: 1204


🏁 Script executed:

# Search for other uses of getRequest to see error handling patterns
rg "getRequest\(" src/actions/ -A 6 -B 1 | head -100

Repository: fntechgit/summit-admin

Length of output: 6135


🏁 Script executed:

# Search for authErrorHandler patterns with catch or finally
rg "authErrorHandler.*\)" -A 8 | grep -E "(catch|finally)" -B 5

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Search for postRequest patterns to see if they use different error handling
rg "postRequest\(" src/actions/ -A 6 | grep -E "(catch|finally|\.then)" -B 2 -A 2 | head -60

Repository: fntechgit/summit-admin

Length of output: 2526


🏁 Script executed:

# Search for .catch() handlers in actions to understand error handling patterns
rg "\.catch\(" src/actions/ -B 3 -A 3 | head -80

Repository: fntechgit/summit-admin

Length of output: 4194


🏁 Script executed:

# Check sponsor-forms-actions.js to see those catch patterns in context
rg "need to catch promise reject" src/actions/sponsor-forms-actions.js -B 10 -A 3 | head -80

Repository: fntechgit/summit-admin

Length of output: 2389


🏁 Script executed:

# Let's verify the exact getSponsorPages implementation one more time to be certain
sed -n '60,100p' src/actions/sponsor-pages-actions.js

Repository: fntechgit/summit-admin

Length of output: 1336


Ensure loading state clears on request failure.

stopLoading() runs only on success; if getRequest rejects, the spinner can remain stuck. Similar functions in the codebase explicitly handle rejection with .catch() and .finally() patterns (see sponsor-forms-actions.js with comments "need to catch promise reject").

🐛 Proposed fix
    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());
-    });
+    )(params)(dispatch).finally(() => {
+      dispatch(stopLoading());
+    });
📝 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
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());
});
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).finally(() => {
dispatch(stopLoading());
});
🤖 Prompt for AI Agents
In `@src/actions/sponsor-pages-actions.js` around lines 85 - 93, The current
getRequest(...) call in sponsor-pages-actions.js (using
createAction(REQUEST_SPONSOR_PAGES), createAction(RECEIVE_SPONSOR_PAGES),
authErrorHandler, etc.) only dispatches stopLoading() in the .then path so a
rejected request leaves the spinner running; update the promise handling on the
getRequest(...) invocation to catch rejections and ensure stopLoading() is
always dispatched (use .catch(...) to handle errors and/or .finally(...) to call
dispatch(stopLoading())), preserving existing error handling via
authErrorHandler and the original action creators.

};

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")
})
);
})
.finally(() => dispatch(stopLoading()));
};

Choose a reason for hiding this comment

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

missing finally stopLoading

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
8 changes: 5 additions & 3 deletions src/components/mui/formik-inputs/mui-formik-datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
import { useField } from "formik";

const MuiFormikDatepicker = ({ name, label, required }) => {
const MuiFormikDatepicker = ({ name, label, required, ...props }) => {
const [field, meta, helpers] = useField(name);
const requiredLabel = `${label} *`;
return (
Expand All @@ -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
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

🧩 Analysis chain

🏁 Script executed:

find . -name "mui-formik-datepicker.js" -type f

Repository: fntechgit/summit-admin

Length of output: 125


🏁 Script executed:

head -50 src/components/mui/formik-inputs/mui-formik-datepicker.js

Repository: fntechgit/summit-admin

Length of output: 1536


🌐 Web query:

MUI X DatePicker margin prop API documentation

💡 Result:

In MUI X DatePicker, the margin prop is not a direct DatePicker prop; it belongs to the underlying field component (a pickers TextField).

  • API docs for margin: it’s documented on PickersTextField (and also on DateField) as margin: 'none' | 'dense' | 'normal' with default 'none'. [1], [2]

  • How to set it on DatePicker (current MUI X): pass it to the field via slotProps.textField:

    <DatePicker slotProps={{ textField: { margin: 'dense' } }} />

    This “Customize the TextField” approach is shown in the Custom field docs. [3]

Sources:
[1] PickersTextField API (margin) (mui.com)
[2] DateField API (margin) (mui.com)
[3] Custom field docs (use slotProps.textField to pass TextField props) (mui.com)

Citations:


🌐 Web query:

"DatePicker" "slotProps" "textField" margin MUI X

💡 Result:

To set the margin on the input used by MUI X DatePicker, pass it through the textField slotProps:

<DatePicker
  label="Start date"
  slotProps={{
    textField: { margin: 'dense' }, // or 'normal' | 'none'
  }}
/>

slotProps.textField forwards props to the underlying picker field TextField, and margin is a supported prop ('dense' | 'normal' | 'none', default 'none'). [1], [2]


margin="normal" won't be applied on DatePicker.

MUI X DatePicker does not accept a margin prop directly—it must be passed through slotProps.textField. Placing it on the component level will be silently ignored. Move margin into slotProps.textField for it to take effect.

🐛 Proposed fix
         slotProps={{
           textField: {
             name,
             label: required ? requiredLabel : label,
             error: meta.touched && Boolean(meta.error),
             helperText: meta.touched && meta.error,
-            fullWidth: true
+            fullWidth: true,
+            margin: "normal"
           },
           day: {
             sx: {
               fontSize: "1.2rem",
               fontWeight: 600
             }
           },
           layout: {
             sx: {
               "& .MuiDayCalendar-weekDayLabel": {
                 fontSize: "1rem"
               }
             }
           }
         }}
-        margin="normal"
         // eslint-disable-next-line react/jsx-props-no-spreading
         {...props}
🤖 Prompt for AI Agents
In `@src/components/mui/formik-inputs/mui-formik-datepicker.js` at line 22, The
DatePicker's margin prop is currently set at the component level and is ignored;
update the mui DatePicker usage in mui-formik-datepicker (the DatePicker
component instance) to move margin="normal" into slotProps.textField (e.g.,
slotProps={{ textField: { margin: "normal", ... } }}) so the TextField receives
the margin; ensure any existing slotProps.textField merging preserves other
props.

},
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,
marginWrapper = "normal",
options,
...props
}) => {
const [field, meta] = useField({ name });

return (
<FormControl
fullWidth
margin="normal"
margin={marginWrapper}
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,
marginWrapper: 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 !== "";
const shouldShrink = hasValue || Boolean(placeholder);

return (
<FormControl fullWidth error={meta.touched && Boolean(meta.error)}>
{label && (
<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={shouldShrink}
displayEmpty
renderValue={(selected) => {
if (!selected || selected === "") {
Expand Down
Loading