From 5a1a71761b6317939a5b2e99b1ace0183f43cd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Fri, 20 Mar 2026 16:57:13 -0300 Subject: [PATCH 1/7] fix: add popup to manual scan badge on sponsor badge scans tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .env.example | 2 +- src/actions/sponsor-actions.js | 29 +++ src/components/mui/mui-qr-badge-popup.js | 178 ++++++++++++++++++ src/i18n/en.json | 6 + .../tabs/sponsor-badge-scans/index.js | 39 +++- 5 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 src/components/mui/mui-qr-badge-popup.js diff --git a/.env.example b/.env.example index d0facb2fc..1ac7e9a39 100644 --- a/.env.example +++ b/.env.example @@ -20,7 +20,7 @@ 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 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} ${DROPBOX_MATERIALIZER_API_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" +SCOPES="profile openid offline_access reports/all ${EMAIL_SCOPES} ${INVENTORY_API_SCOPES} ${FILE_UPLOAD_SCOPES} ${PURCHASES_API_SCOPES} ${SPONSOR_USERS_SCOPES} ${SPONSOR_PAGES_SCOPES} ${DROPBOX_MATERIALIZER_API_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 ${SCOPES_BASE_REALM}/summits/badge-scans/write 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" diff --git a/src/actions/sponsor-actions.js b/src/actions/sponsor-actions.js index 34779011f..127c80e2a 100644 --- a/src/actions/sponsor-actions.js +++ b/src/actions/sponsor-actions.js @@ -109,6 +109,7 @@ export const REQUEST_BADGE_SCANS = "REQUEST_BADGE_SCANS"; export const RECEIVE_BADGE_SCANS = "RECEIVE_BADGE_SCANS"; export const RECEIVE_BADGE_SCAN = "RECEIVE_BADGE_SCAN"; export const BADGE_SCAN_UPDATED = "BADGE_SCAN_UPDATED"; +export const BADGE_SCAN_ADDED = "BADGE_SCAN_ADDED"; export const RESET_BADGE_SCAN_FORM = "RESET_BADGE_SCAN_FORM"; export const RECEIVE_SPONSORS_WITH_SCANS = "RECEIVE_SPONSORS_WITH_SCANS"; @@ -1472,6 +1473,34 @@ export const saveBadgeScan = (entity) => async (dispatch, getState) => { }); }; +export const addBadgeScan = (entity) => async (dispatch, getState) => { + const { currentSummitState } = getState(); + const accessToken = await getAccessTokenSafely(); + const { currentSummit } = currentSummitState; + + dispatch(startLoading()); + + const params = { + access_token: accessToken + }; + + return postRequest( + null, + createAction(BADGE_SCAN_ADDED), + `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`, + entity, + snackbarErrorHandler + )(params)(dispatch).then(() => { + dispatch(stopLoading()); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_badge_scan.badge_scan_saved") + }) + ); + }); +}; + export const resetBadgeScanForm = () => (dispatch) => { dispatch(createAction(RESET_BADGE_SCAN_FORM)({})); }; diff --git a/src/components/mui/mui-qr-badge-popup.js b/src/components/mui/mui-qr-badge-popup.js new file mode 100644 index 000000000..abdaf459f --- /dev/null +++ b/src/components/mui/mui-qr-badge-popup.js @@ -0,0 +1,178 @@ +/** + * Copyright 2019 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 moment from "moment-timezone"; +import T from "i18n-react/dist/i18n-react"; +import { FormikProvider, useFormik } from "formik"; +import ExtraQuestionsMUI from "openstack-uicore-foundation/lib/components/extra-questions-mui"; +import { + Alert, + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + IconButton, + InputLabel, + Typography +} from "@mui/material"; +import CloseIcon from "@mui/icons-material/Close"; +import { useSnackbarMessage } from "./SnackbarNotification/Context"; +import MuiFormikTextField from "./formik-inputs/mui-formik-textfield"; +import QrReader from "../qr-reader"; +import { getTypeValue, toSlug } from "../../utils/extra-questions"; + +const buildInitialValues = (extraQuestions) => { + const values = { notes: "" }; + extraQuestions.forEach((q) => { + values[toSlug(q.name, q.id)] = getTypeValue("", q.type); + }); + return values; +}; + +const MuiQrBadgePopup = ({ onClose, onScan, extraQuestions = [], isAdmin }) => { + const { errorMessage } = useSnackbarMessage(); + const [scannedCode, setScannedCode] = useState(null); + + const formik = useFormik({ + initialValues: buildInitialValues(extraQuestions), + onSubmit: (values) => { + const { notes, ...extraValues } = values; + + const extra_questions = Object.entries(extraValues) + .map(([slug, value]) => ({ + question_id: parseInt(slug.split("_").pop()), + answer: Array.isArray(value) + ? value.filter((v) => v !== "").join(",") + : value + })) + .filter((q) => q.answer); + + onScan({ + qr_code: scannedCode, + scan_date: moment().unix(), + notes, + extra_questions + }); + } + }); + + const handleScan = (data) => { + if (!data) return; + setScannedCode(data); + }; + + const handleRescan = () => { + setScannedCode(null); + }; + + const handleError = () => { + errorMessage(T.translate("sponsor_badge_scans.scan_popup.error")); + }; + + return ( + + + + {T.translate("sponsor_badge_scans.scan_popup.scan_qr")} + + + + + + + + + + {scannedCode ? ( + + {T.translate("sponsor_badge_scans.scan_popup.rescan")} + + } + > + {T.translate("sponsor_badge_scans.scan_popup.badge_scanned")} + + ) : ( + + + + )} + + {isAdmin && ( + + + {T.translate("edit_badge_scan.notes")} + + + + )} + + {extraQuestions.length > 0 && isAdmin && ( + <> + + {T.translate("edit_badge_scan.extra_questions")} + + a.order - b.order + )} + formik={formik} + allowEdit + /> + + )} + + + + + + + + + ); +}; + +export default MuiQrBadgePopup; diff --git a/src/i18n/en.json b/src/i18n/en.json index 09acb8d4a..549ee018e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2606,6 +2606,12 @@ "last_name": "Last Name", "email": "Email", "company": "Company", + "scan_popup": { + "scan_qr": "Scan QR", + "error": "Cannot read QR code, please try again", + "badge_scanned": "Badge scanned successfully", + "rescan": "Re-scan" + }, "placeholders": { "search": "Search..." } diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js index e998d27f1..7ac93f707 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js @@ -23,12 +23,16 @@ import { getBadgeScans, exportBadgeScans, getBadgeScan, - saveBadgeScan -} from "../../../../../actions/sponsor-actions"; -import { DEFAULT_CURRENT_PAGE } from "../../../../../utils/constants"; + saveBadgeScan, + addBadgeScan +} from "../../../actions/sponsor-actions"; +import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; import EditBadgeScanPopup from "./edit-badge-scan-popup"; +import MuiQrBadgePopup from "../../../components/mui/mui-qr-badge-popup"; +import Member from "../../../models/member"; const SponsorBadgeScans = ({ + member, sponsor, badgeScans, totalBadgeScans, @@ -41,14 +45,20 @@ const SponsorBadgeScans = ({ exportBadgeScans, getBadgeScan, saveBadgeScan, + addBadgeScan, currentBadgeScan }) => { useEffect(() => { if (sponsor?.id) getBadgeScans(sponsor.id); }, [sponsor]); + const memberObj = new Member(member); + const isAdmin = memberObj.hasAccess("admin-sponsors"); + const [searchTerm, setSearchTerm] = useState(term); const [showEditBadgeScanPopup, setShowEditBadgeScanPopup] = useState(false); + const [showManualBadgeScanPopup, setShowManualBadgeScanPopup] = + useState(false); const handleSearch = (ev) => { if (ev.key === "Enter") { @@ -94,7 +104,15 @@ const SponsorBadgeScans = ({ saveBadgeScan(badgeScan).then(() => setShowEditBadgeScanPopup(false)); }; - const handleNewManualScan = () => {}; + const handleNewManualScan = () => { + setShowManualBadgeScanPopup(true); + }; + + const handleManualScanSubmit = (entity) => { + addBadgeScan(entity) + .then(() => getBadgeScans(sponsor.id)) + .finally(() => setShowManualBadgeScanPopup(false)); + }; const handleExportBadgeScans = () => { exportBadgeScans(sponsor); @@ -233,17 +251,27 @@ const SponsorBadgeScans = ({ onSubmit={handleBadgeScanSave} /> )} + {showManualBadgeScanPopup && ( + setShowManualBadgeScanPopup(false)} + extraQuestions={sponsor.extra_questions} + isAdmin={isAdmin} + /> + )} ); }; const mapStateToProps = ({ + loggedUserState, badgeScansListState, currentBadgeScanState, currentSponsorState }) => ({ ...badgeScansListState, currentBadgeScan: currentBadgeScanState.entity, + member: loggedUserState.member, sponsor: currentSponsorState.entity }); @@ -251,5 +279,6 @@ export default connect(mapStateToProps, { getBadgeScans, exportBadgeScans, getBadgeScan, - saveBadgeScan + saveBadgeScan, + addBadgeScan })(SponsorBadgeScans); From 3c12db3394ed0e04ac1cdf689cb91a345581598e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Thu, 26 Mar 2026 12:57:09 -0300 Subject: [PATCH 2/7] fix: disable button on submit, adjust actions, fix bug with question checkboxlist answers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/sponsor-actions.js | 198 +++++++++--------- src/components/mui/mui-qr-badge-popup.js | 6 +- .../tabs/sponsor-badge-scans/index.js | 16 +- src/utils/extra-questions.js | 2 +- 4 files changed, 119 insertions(+), 103 deletions(-) diff --git a/src/actions/sponsor-actions.js b/src/actions/sponsor-actions.js index 127c80e2a..a4ada1494 100644 --- a/src/actions/sponsor-actions.js +++ b/src/actions/sponsor-actions.js @@ -309,7 +309,7 @@ export const getSponsor = (sponsorId) => async (dispatch, getState) => { const params = { access_token: accessToken, expand: - "company,members,sponsorships,sponsorships.type,featured_event,extra_questions,lead_report_setting", + "company,members,sponsorships,sponsorships.type,featured_event,extra_questions,extra_questions.values,lead_report_setting", fields: "featured_event.id,featured_event.title,sponsorships.id,sponsorships.type.id,sponsorships.type.type_id" }; @@ -344,15 +344,16 @@ export const addSponsorToSummit = (entity) => async (dispatch, getState) => { normalizedEntity, snackbarErrorHandler, entity - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("sponsor_list.sponsor_added") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_list.sponsor_added") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; export const getSponsorTiers = @@ -419,15 +420,16 @@ export const addTierToSponsor = normalizedSponsorships, snackbarErrorHandler, sponsorships - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_sponsor.sponsorship_added") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.sponsorship_added") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; export const removeTierFromSponsor = @@ -451,15 +453,16 @@ export const removeTierFromSponsor = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsorships/${sponsorshipId}`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_sponsor.sponsorship_removed") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.sponsorship_removed") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; const normalizeSponsorToAdd = (entity) => { @@ -596,15 +599,16 @@ export const removeAddonToSponsorship = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsorships/${sponsorshipId}/add-ons/${addonId}`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_sponsor.sponsorship_addon_removed") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_sponsor.sponsorship_addon_removed") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; const normalizeAddons = (entity) => { @@ -861,15 +865,16 @@ export const deleteExtraQuestion = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/extra-questions/${questionId}`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.done"), - html: T.translate("edit_sponsor.extra_question_deleted") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.done"), + html: T.translate("edit_sponsor.extra_question_deleted") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; export const saveSponsorExtraQuestion = @@ -905,15 +910,16 @@ export const saveSponsorExtraQuestion = normalizedEntity, snackbarErrorHandler, entity - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.done"), - html: T.translate("edit_sponsor.extra_question_saved") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.done"), + html: T.translate("edit_sponsor.extra_question_saved") + }) + ); + }) + .finally(() => dispatch(stopLoading())); } return postRequest( @@ -923,16 +929,17 @@ export const saveSponsorExtraQuestion = normalizedEntity, snackbarErrorHandler, entity - )(params)(dispatch).then(({ response }) => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.done"), - html: T.translate("edit_sponsor.extra_question_created") - }) - ); - return response; - }); + )(params)(dispatch) + .then(({ response }) => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.done"), + html: T.translate("edit_sponsor.extra_question_created") + }) + ); + return response; + }) + .finally(() => dispatch(stopLoading())); }; export const getSponsorExtraQuestion = @@ -1267,15 +1274,16 @@ export const deleteSummitSponsorship = `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/sponsorships-types/${sponsorshipId}`, null, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("summit_sponsorship_list.tier_deleted") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("summit_sponsorship_list.tier_deleted") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; const normalizeSponsorship = (entity) => { @@ -1437,9 +1445,7 @@ export const getBadgeScan = (scanId) => async (dispatch, getState) => { createAction(RECEIVE_BADGE_SCAN), `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans/${scanId}`, authErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - }); + )(params)(dispatch).finally(() => dispatch(stopLoading())); }; export const saveBadgeScan = (entity) => async (dispatch, getState) => { @@ -1462,15 +1468,16 @@ export const saveBadgeScan = (entity) => async (dispatch, getState) => { normalizedEntity, snackbarErrorHandler, entity - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_badge_scan.badge_scan_saved") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_badge_scan.badge_scan_saved") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; export const addBadgeScan = (entity) => async (dispatch, getState) => { @@ -1490,15 +1497,16 @@ export const addBadgeScan = (entity) => async (dispatch, getState) => { `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/badge-scans`, entity, snackbarErrorHandler - )(params)(dispatch).then(() => { - dispatch(stopLoading()); - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("edit_badge_scan.badge_scan_saved") - }) - ); - }); + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("edit_badge_scan.badge_scan_saved") + }) + ); + }) + .finally(() => dispatch(stopLoading())); }; export const resetBadgeScanForm = () => (dispatch) => { diff --git a/src/components/mui/mui-qr-badge-popup.js b/src/components/mui/mui-qr-badge-popup.js index abdaf459f..0d1c20f1e 100644 --- a/src/components/mui/mui-qr-badge-popup.js +++ b/src/components/mui/mui-qr-badge-popup.js @@ -43,7 +43,7 @@ const buildInitialValues = (extraQuestions) => { return values; }; -const MuiQrBadgePopup = ({ onClose, onScan, extraQuestions = [], isAdmin }) => { +const MuiQrBadgePopup = ({ onClose, onSave, extraQuestions = [], isAdmin }) => { const { errorMessage } = useSnackbarMessage(); const [scannedCode, setScannedCode] = useState(null); @@ -61,7 +61,7 @@ const MuiQrBadgePopup = ({ onClose, onScan, extraQuestions = [], isAdmin }) => { })) .filter((q) => q.answer); - onScan({ + return onSave({ qr_code: scannedCode, scan_date: moment().unix(), notes, @@ -164,7 +164,7 @@ const MuiQrBadgePopup = ({ onClose, onScan, extraQuestions = [], isAdmin }) => { type="submit" fullWidth variant="contained" - disabled={!scannedCode} + disabled={!scannedCode || formik.isSubmitting} > {T.translate("general.save")} diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js index 7ac93f707..dad2e6237 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js @@ -109,9 +109,17 @@ const SponsorBadgeScans = ({ }; const handleManualScanSubmit = (entity) => { - addBadgeScan(entity) - .then(() => getBadgeScans(sponsor.id)) - .finally(() => setShowManualBadgeScanPopup(false)); + addBadgeScan(entity).then(() => { + setShowManualBadgeScanPopup(false); + return getBadgeScans( + sponsor.id, + term, + DEFAULT_CURRENT_PAGE, + perPage, + order, + orderDir + ); + }); }; const handleExportBadgeScans = () => { @@ -253,7 +261,7 @@ const SponsorBadgeScans = ({ )} {showManualBadgeScanPopup && ( setShowManualBadgeScanPopup(false)} extraQuestions={sponsor.extra_questions} isAdmin={isAdmin} diff --git a/src/utils/extra-questions.js b/src/utils/extra-questions.js index 375ccd8ce..c9c545dbe 100644 --- a/src/utils/extra-questions.js +++ b/src/utils/extra-questions.js @@ -18,7 +18,7 @@ export const getTypeValue = (ans, type) => { case QuestionType_Checkbox: return ans === "true"; case QuestionType_CheckBoxList: - return ans?.split(",") || []; + return ans ? ans.split(",") : []; case QuestionType_CountryComboBox: case QuestionType_ComboBox: return ans || ""; From 27e365a45967e2bd76e981f6c505021ac61a2eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 14 Apr 2026 00:03:46 -0300 Subject: [PATCH 3/7] fix: add radio button, allow to set attendee or qr scan code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/attendee-actions.js | 26 ++++ src/components/mui/mui-qr-badge-popup.js | 125 ++++++++++++++---- src/i18n/en.json | 4 +- .../tabs/sponsor-badge-scans/index.js | 8 +- 4 files changed, 134 insertions(+), 29 deletions(-) diff --git a/src/actions/attendee-actions.js b/src/actions/attendee-actions.js index 460511120..fc5f47ea4 100644 --- a/src/actions/attendee-actions.js +++ b/src/actions/attendee-actions.js @@ -809,3 +809,29 @@ export const queryPaidAttendees = _.debounce( }, DEBOUNCE_WAIT ); + +export const queryAttendees = _.debounce(async (input, summitId, callback) => { + const accessToken = await getAccessTokenSafely(); + + const endpoint = URI( + `${window.API_BASE_URL}/api/v1/summits/${summitId}/attendees` + ); + + input = escapeFilterValue(input); + endpoint.addQuery("access_token", accessToken); + endpoint.addQuery("order", "first_name,last_name"); + endpoint.addQuery("page", 1); + endpoint.addQuery("per_page", DEFAULT_PER_PAGE); + + if (input) { + endpoint.addQuery("filter[]", `full_name=@${input},email=@${input}`); + } + + fetch(endpoint) + .then(fetchResponseHandler) + .then((json) => { + const options = [...json.data]; + callback(options); + }) + .catch(fetchErrorHandler); +}, DEBOUNCE_WAIT); diff --git a/src/components/mui/mui-qr-badge-popup.js b/src/components/mui/mui-qr-badge-popup.js index 0d1c20f1e..cf26e58b6 100644 --- a/src/components/mui/mui-qr-badge-popup.js +++ b/src/components/mui/mui-qr-badge-popup.js @@ -25,8 +25,11 @@ import { DialogContent, DialogTitle, Divider, + FormControlLabel, IconButton, InputLabel, + Radio, + RadioGroup, Typography } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; @@ -34,23 +37,35 @@ import { useSnackbarMessage } from "./SnackbarNotification/Context"; import MuiFormikTextField from "./formik-inputs/mui-formik-textfield"; import QrReader from "../qr-reader"; import { getTypeValue, toSlug } from "../../utils/extra-questions"; +import MuiFormikAsyncAutocomplete from "./formik-inputs/mui-formik-async-select"; +import { queryAttendees } from "../../actions/attendee-actions"; const buildInitialValues = (extraQuestions) => { - const values = { notes: "" }; + const values = { notes: "", attendee_email: "" }; extraQuestions.forEach((q) => { values[toSlug(q.name, q.id)] = getTypeValue("", q.type); }); return values; }; -const MuiQrBadgePopup = ({ onClose, onSave, extraQuestions = [], isAdmin }) => { +const BADGE_SCAN_MODE_QR = "qr"; +const BADGE_SCAN_MODE_ATTENDEE = "attendee"; + +const MuiQrBadgePopup = ({ + onClose, + onSave, + extraQuestions = [], + isAdmin, + summitId +}) => { const { errorMessage } = useSnackbarMessage(); const [scannedCode, setScannedCode] = useState(null); + const [scanMode, setScanMode] = useState(null); const formik = useFormik({ initialValues: buildInitialValues(extraQuestions), onSubmit: (values) => { - const { notes, ...extraValues } = values; + const { attendee_email, notes, ...extraValues } = values; const extra_questions = Object.entries(extraValues) .map(([slug, value]) => ({ @@ -61,15 +76,24 @@ const MuiQrBadgePopup = ({ onClose, onSave, extraQuestions = [], isAdmin }) => { })) .filter((q) => q.answer); - return onSave({ - qr_code: scannedCode, + const entity = { + ...(scanMode === BADGE_SCAN_MODE_QR + ? { qr_code: scannedCode } + : { attendee_email: attendee_email.value }), scan_date: moment().unix(), notes, extra_questions - }); + }; + + return onSave(entity); } }); + const handleScanModeChange = (e) => { + setScanMode(e.target.value); + setScannedCode(null); + }; + const handleScan = (data) => { if (!data) return; setScannedCode(data); @@ -83,6 +107,12 @@ const MuiQrBadgePopup = ({ onClose, onSave, extraQuestions = [], isAdmin }) => { errorMessage(T.translate("sponsor_badge_scans.scan_popup.error")); }; + const isSubmitDisabled = + formik.isSubmitting || + !scanMode || + (scanMode === BADGE_SCAN_MODE_QR && !scannedCode) || + (scanMode === BADGE_SCAN_MODE_ATTENDEE && !formik.values.attendee_email); + return ( { autoComplete="off" > - {scannedCode ? ( - - {T.translate("sponsor_badge_scans.scan_popup.rescan")} - - } - > - {T.translate("sponsor_badge_scans.scan_popup.badge_scanned")} - - ) : ( + + } + label={T.translate("sponsor_badge_scans.scan_popup.scan_qr")} + /> + } + label={T.translate("sponsor_badge_scans.scan_popup.attendee")} + /> + + + {scanMode === BADGE_SCAN_MODE_QR && + (scannedCode ? ( + + {T.translate("sponsor_badge_scans.scan_popup.rescan")} + + } + > + {T.translate("sponsor_badge_scans.scan_popup.badge_scanned")} + + ) : ( + + + + ))} + + {scanMode === BADGE_SCAN_MODE_ATTENDEE && ( - + + {T.translate("sponsor_badge_scans.scan_popup.attendee")} + + ({ + value: attendee.email.toString(), + label: `${attendee.first_name || ""} ${ + attendee.last_name || "" + } (${attendee.email || attendee.id})` + })} + /> )} @@ -164,7 +237,7 @@ const MuiQrBadgePopup = ({ onClose, onSave, extraQuestions = [], isAdmin }) => { type="submit" fullWidth variant="contained" - disabled={!scannedCode || formik.isSubmitting} + disabled={isSubmitDisabled} > {T.translate("general.save")} diff --git a/src/i18n/en.json b/src/i18n/en.json index 549ee018e..b14f7aa73 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2608,9 +2608,11 @@ "company": "Company", "scan_popup": { "scan_qr": "Scan QR", + "attendee": "Attendee", "error": "Cannot read QR code, please try again", "badge_scanned": "Badge scanned successfully", - "rescan": "Re-scan" + "rescan": "Re-scan", + "attendee_placeholder": "Enter Attendee..." }, "placeholders": { "search": "Search..." diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js index dad2e6237..dc91c65c4 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js @@ -34,6 +34,7 @@ import Member from "../../../models/member"; const SponsorBadgeScans = ({ member, sponsor, + summitId, badgeScans, totalBadgeScans, term, @@ -265,6 +266,7 @@ const SponsorBadgeScans = ({ onClose={() => setShowManualBadgeScanPopup(false)} extraQuestions={sponsor.extra_questions} isAdmin={isAdmin} + summitId={summitId} /> )} @@ -275,12 +277,14 @@ const mapStateToProps = ({ loggedUserState, badgeScansListState, currentBadgeScanState, - currentSponsorState + currentSponsorState, + currentSummitState }) => ({ ...badgeScansListState, currentBadgeScan: currentBadgeScanState.entity, member: loggedUserState.member, - sponsor: currentSponsorState.entity + sponsor: currentSponsorState.entity, + summitId: currentSummitState.currentSummit.id }); export default connect(mapStateToProps, { From e6b3cc809444efeab9f765e595badf327f69f500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 14 Apr 2026 00:32:23 -0300 Subject: [PATCH 4/7] fix: add unit test file for comoponent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- .../mui/__tests__/mui-qr-badge-popup.test.js | 361 ++++++++++++++++++ 1 file changed, 361 insertions(+) create mode 100644 src/components/mui/__tests__/mui-qr-badge-popup.test.js diff --git a/src/components/mui/__tests__/mui-qr-badge-popup.test.js b/src/components/mui/__tests__/mui-qr-badge-popup.test.js new file mode 100644 index 000000000..8c5a6ee0e --- /dev/null +++ b/src/components/mui/__tests__/mui-qr-badge-popup.test.js @@ -0,0 +1,361 @@ +// mui-qr-badge-popup.test.js +import React from "react"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import "@testing-library/jest-dom"; +import MuiQrBadgePopup from "../mui-qr-badge-popup"; + +jest.mock("i18n-react/dist/i18n-react", () => ({ + __esModule: true, + default: { translate: (key) => key } +})); + +jest.mock("moment-timezone", () => { + const mockMoment = jest.fn(() => ({ unix: () => 1234567890 })); + return mockMoment; +}); + +jest.mock("../../qr-reader", () => { + const React = require("react"); + return { + __esModule: true, + default: ({ onScan, onError }) => ( +
+ + +
+ ) + }; +}); + +jest.mock("../formik-inputs/mui-formik-async-select", () => { + const React = require("react"); + const { useField } = require("formik"); + return { + __esModule: true, + default: ({ name, placeholder }) => { + const [field, , helpers] = useField(name); + return ( + + helpers.setValue({ value: e.target.value, label: e.target.value }) + } + /> + ); + } + }; +}); + +jest.mock( + "openstack-uicore-foundation/lib/components/extra-questions-mui", + () => ({ + __esModule: true, + default: () =>
+ }) +); + +const mockErrorMessage = jest.fn(); +jest.mock("../SnackbarNotification/Context", () => ({ + useSnackbarMessage: () => ({ errorMessage: mockErrorMessage }) +})); + +jest.mock("../../../actions/attendee-actions", () => ({ + queryAttendees: jest.fn() +})); + +jest.mock("@mui/material/IconButton", () => { + const React = require("react"); + return { + __esModule: true, + default: ({ children, onClick, ...rest }) => ( + + ) + }; +}); + +const defaultProps = { + onClose: jest.fn(), + onSave: jest.fn(), + extraQuestions: [], + isAdmin: false, + summitId: 123 +}; + +const renderComponent = (props = {}) => + render(); + +describe("MuiQrBadgePopup", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("Initialization", () => { + it("should render radio buttons, no content panels, and a disabled submit button", () => { + renderComponent(); + + expect( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ).toBeInTheDocument(); + expect( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.attendee" + }) + ).toBeInTheDocument(); + expect(screen.queryByTestId("qr-reader")).not.toBeInTheDocument(); + expect( + screen.queryByTestId("async-select-attendee_email") + ).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "general.save" }) + ).toBeDisabled(); + }); + + it("should call onClose when close button is clicked", async () => { + const onClose = jest.fn(); + renderComponent({ onClose }); + + await userEvent.click(screen.getByRole("button", { name: "" })); + + expect(onClose).toHaveBeenCalled(); + }); + }); + + describe("QR scan mode", () => { + it("should show QR reader and keep submit disabled when QR mode is selected", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + + expect(screen.getByTestId("qr-reader")).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "general.save" }) + ).toBeDisabled(); + }); + + it("should show success alert, hide QR reader, and enable submit after scanning", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + await userEvent.click(screen.getByText("Simulate Scan")); + + expect( + screen.getByText("sponsor_badge_scans.scan_popup.badge_scanned") + ).toBeInTheDocument(); + expect(screen.queryByTestId("qr-reader")).not.toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "general.save" }) + ).not.toBeDisabled(); + }); + + it("should restore QR reader when rescan button is clicked", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + await userEvent.click(screen.getByText("Simulate Scan")); + await userEvent.click( + screen.getByRole("button", { + name: "sponsor_badge_scans.scan_popup.rescan" + }) + ); + + expect(screen.getByTestId("qr-reader")).toBeInTheDocument(); + expect( + screen.queryByText("sponsor_badge_scans.scan_popup.badge_scanned") + ).not.toBeInTheDocument(); + }); + + it("should call error handler when QR scan fails", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + await userEvent.click(screen.getByText("Simulate Error")); + + expect(mockErrorMessage).toHaveBeenCalledWith( + "sponsor_badge_scans.scan_popup.error" + ); + }); + + it("should submit payload with qr_code only and no attendee_email", async () => { + const onSave = jest.fn(); + renderComponent({ onSave }); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + await userEvent.click(screen.getByText("Simulate Scan")); + await userEvent.click( + screen.getByRole("button", { name: "general.save" }) + ); + + await waitFor(() => { + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ qr_code: "TEST_QR_CODE" }) + ); + const [payload] = onSave.mock.calls[0]; + expect(payload).not.toHaveProperty("attendee_email"); + }); + }); + }); + + describe("Attendee mode", () => { + it("should show attendee autocomplete and keep submit disabled when attendee mode is selected", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.attendee" + }) + ); + + expect( + screen.getByTestId("async-select-attendee_email") + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: "general.save" }) + ).toBeDisabled(); + }); + + it("should enable submit and send attendee_email only after selecting an attendee", async () => { + const onSave = jest.fn(); + renderComponent({ onSave }); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.attendee" + }) + ); + await userEvent.type( + screen.getByTestId("async-select-attendee_email"), + "john@example.com" + ); + + expect( + screen.getByRole("button", { name: "general.save" }) + ).not.toBeDisabled(); + + await userEvent.click( + screen.getByRole("button", { name: "general.save" }) + ); + + await waitFor(() => { + expect(onSave).toHaveBeenCalledWith( + expect.objectContaining({ attendee_email: "john@example.com" }) + ); + const [payload] = onSave.mock.calls[0]; + expect(payload).not.toHaveProperty("qr_code"); + }); + }); + }); + + describe("Mode switching", () => { + it("should hide QR reader when switching from QR to attendee mode", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + expect(screen.getByTestId("qr-reader")).toBeInTheDocument(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.attendee" + }) + ); + + expect(screen.queryByTestId("qr-reader")).not.toBeInTheDocument(); + expect( + screen.getByTestId("async-select-attendee_email") + ).toBeInTheDocument(); + }); + + it("should reset scanned QR code when switching mode", async () => { + renderComponent(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + await userEvent.click(screen.getByText("Simulate Scan")); + expect( + screen.getByText("sponsor_badge_scans.scan_popup.badge_scanned") + ).toBeInTheDocument(); + + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.attendee" + }) + ); + await userEvent.click( + screen.getByRole("radio", { + name: "sponsor_badge_scans.scan_popup.scan_qr" + }) + ); + + expect(screen.getByTestId("qr-reader")).toBeInTheDocument(); + expect( + screen.queryByText("sponsor_badge_scans.scan_popup.badge_scanned") + ).not.toBeInTheDocument(); + }); + }); + + describe("Admin-only fields", () => { + const extraQuestions = [ + { id: 1, name: "Company", type: "Text", order: 1 }, + { id: 2, name: "Title", type: "Text", order: 2 } + ]; + + it("should show notes field and extra questions for admin when questions are provided", () => { + renderComponent({ isAdmin: true, extraQuestions }); + + expect(screen.getByText("edit_badge_scan.notes")).toBeInTheDocument(); + expect(screen.getByTestId("extra-questions")).toBeInTheDocument(); + }); + + it("should not show notes field or extra questions for non-admin users", () => { + renderComponent({ isAdmin: false, extraQuestions }); + + expect( + screen.queryByText("edit_badge_scan.notes") + ).not.toBeInTheDocument(); + expect(screen.queryByTestId("extra-questions")).not.toBeInTheDocument(); + }); + + it("should not show extra questions when list is empty even for admin", () => { + renderComponent({ isAdmin: true, extraQuestions: [] }); + + expect(screen.queryByTestId("extra-questions")).not.toBeInTheDocument(); + }); + }); +}); From d407efb14081ec3d3f21de2f2aa29daed69dce13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 21 Apr 2026 16:35:39 -0300 Subject: [PATCH 5/7] fix: adjust imports paths on sponsor badge scans --- src/components/mui/mui-qr-badge-popup.js | 2 +- .../sponsor-page/tabs/sponsor-badge-scans/index.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/mui/mui-qr-badge-popup.js b/src/components/mui/mui-qr-badge-popup.js index cf26e58b6..894fd45cf 100644 --- a/src/components/mui/mui-qr-badge-popup.js +++ b/src/components/mui/mui-qr-badge-popup.js @@ -33,7 +33,7 @@ import { Typography } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; -import { useSnackbarMessage } from "./SnackbarNotification/Context"; +import { useSnackbarMessage } from "openstack-uicore-foundation/lib/components/mui/snackbar-notification"; import MuiFormikTextField from "./formik-inputs/mui-formik-textfield"; import QrReader from "../qr-reader"; import { getTypeValue, toSlug } from "../../utils/extra-questions"; diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js index dc91c65c4..60620e20d 100644 --- a/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js +++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-badge-scans/index.js @@ -25,11 +25,11 @@ import { getBadgeScan, saveBadgeScan, addBadgeScan -} from "../../../actions/sponsor-actions"; -import { DEFAULT_CURRENT_PAGE } from "../../../utils/constants"; +} from "../../../../../actions/sponsor-actions"; +import { DEFAULT_CURRENT_PAGE } from "../../../../../utils/constants"; import EditBadgeScanPopup from "./edit-badge-scan-popup"; -import MuiQrBadgePopup from "../../../components/mui/mui-qr-badge-popup"; -import Member from "../../../models/member"; +import MuiQrBadgePopup from "../../../../../components/mui/mui-qr-badge-popup"; +import Member from "../../../../../models/member"; const SponsorBadgeScans = ({ member, From 6bb2bda7109dab82c731ec9f70272b5a1d2a32ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 21 Apr 2026 16:41:04 -0300 Subject: [PATCH 6/7] fix: adjust mock for snackbar message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/components/mui/__tests__/mui-qr-badge-popup.test.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/mui/__tests__/mui-qr-badge-popup.test.js b/src/components/mui/__tests__/mui-qr-badge-popup.test.js index 8c5a6ee0e..923a7dd30 100644 --- a/src/components/mui/__tests__/mui-qr-badge-popup.test.js +++ b/src/components/mui/__tests__/mui-qr-badge-popup.test.js @@ -62,9 +62,12 @@ jest.mock( ); const mockErrorMessage = jest.fn(); -jest.mock("../SnackbarNotification/Context", () => ({ - useSnackbarMessage: () => ({ errorMessage: mockErrorMessage }) -})); +jest.mock( + "openstack-uicore-foundation/lib/components/mui/snackbar-notification", + () => ({ + useSnackbarMessage: () => ({ errorMessage: mockErrorMessage }) + }) +); jest.mock("../../../actions/attendee-actions", () => ({ queryAttendees: jest.fn() From 19491a27b5cfa197fcfb3bbd08e5d1f57e592c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Tue, 21 Apr 2026 18:01:26 -0300 Subject: [PATCH 7/7] fix: update uicore version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 25c0c9721..d83ebe1c6 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "moment": "^2.29.1", "moment-duration-format": "^2.3.2", "moment-timezone": "^0.5.33", - "openstack-uicore-foundation": "5.0.8", + "openstack-uicore-foundation": "5.0.13", "p-limit": "^6.1.0", "path-browserify": "^1.0.1", "postcss-loader": "^6.2.1", diff --git a/yarn.lock b/yarn.lock index b1a7a9cfa..3488b18f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8822,10 +8822,10 @@ open@^10.0.3: is-inside-container "^1.0.0" wsl-utils "^0.1.0" -openstack-uicore-foundation@5.0.8: - version "5.0.8" - resolved "https://registry.yarnpkg.com/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.8.tgz#8ec5900651c1fedbfa4877985a2ffbb1b8cabc52" - integrity sha512-rAbVNayfKDqoUUCrPLiLaU5U10JRXpcUaQqeCebqlwZ/OFS8LTgfhuVChU5ud7NTlskqs5KJUiiq5+vdXgck1Q== +openstack-uicore-foundation@5.0.13: + version "5.0.13" + resolved "https://registry.yarnpkg.com/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.13.tgz#4ca8c0d5505781d589a5b148fe22f71ed4924271" + integrity sha512-SkUgfk5/UweqHmjBxgROu6/doMYJyG+ZbkUDVFoTq6FxBq7nJ8Ma2EqW+oKHuKKbNtPVJiXF3THSIaNFOshlEA== optionator@^0.9.1: version "0.9.4"