From b442777a74cbc8ede319828b3e3c9bb27db0a769 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Thu, 8 Jan 2026 17:41:52 -0300 Subject: [PATCH 1/6] feat: sponsor cart view --- .env.example | 2 +- src/actions/sponsor-cart-actions.js | 116 +++++++++ .../mui/table/extra-rows/NotesRow.jsx | 16 ++ .../mui/table/extra-rows/TotalRow.jsx | 32 +++ src/components/mui/table/extra-rows/index.js | 2 + src/components/mui/table/mui-table.js | 51 ++-- src/i18n/en.json | 16 +- src/pages/sponsors/edit-sponsor-page.js | 6 +- src/pages/sponsors/sponsor-cart-tab/index.js | 231 ++++++++++++++++++ .../sponsor-page-cart-list-reducer.js | 81 ++++++ src/store.js | 2 + src/utils/constants.js | 2 + src/utils/methods.js | 13 +- 13 files changed, 540 insertions(+), 30 deletions(-) create mode 100644 src/actions/sponsor-cart-actions.js create mode 100644 src/components/mui/table/extra-rows/NotesRow.jsx create mode 100644 src/components/mui/table/extra-rows/TotalRow.jsx create mode 100644 src/components/mui/table/extra-rows/index.js create mode 100644 src/pages/sponsors/sponsor-cart-tab/index.js create mode 100644 src/reducers/sponsors/sponsor-page-cart-list-reducer.js diff --git a/.env.example b/.env.example index b71ae142b..370fa00a8 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ 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_SCOPES=purchases-show-medata/read purchases-show-medata/write show-form/read show-form/write +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 my-cart/read my-cart/write 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" EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js new file mode 100644 index 000000000..cbbf74a5a --- /dev/null +++ b/src/actions/sponsor-cart-actions.js @@ -0,0 +1,116 @@ +/** + * 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, + deleteRequest, + startLoading, + stopLoading +} from "openstack-uicore-foundation/lib/utils/actions"; + +import T from "i18n-react"; +import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods"; +import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions"; +import { ERROR_CODE_404 } from "../utils/constants"; + +export const REQUEST_SPONSOR_CART = "REQUEST_SPONSOR_CART"; +export const RECEIVE_SPONSOR_CART = "RECEIVE_SPONSOR_CART"; +export const SPONSOR_CART_FORM_DELETED = "SPONSOR_CART_FORM_DELETED"; + +const customErrorHandler = (err, res) => (dispatch, state) => { + const code = err.status; + dispatch(stopLoading()); + switch (code) { + case ERROR_CODE_404: + break; + default: + authErrorHandler(err, res)(dispatch, state); + } +}; + +export const getSponsorCart = + (term = "") => + async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { + entity: { id: sponsorId } + } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const summitTZ = currentSummit.time_zone.name; + const filter = []; + + dispatch(startLoading()); + + if (term) { + const escapedTerm = escapeFilterValue(term); + filter.push(`name=@${escapedTerm},code=@${escapedTerm}`); + } + + const params = { + access_token: accessToken + }; + + if (filter.length > 0) { + params["filter[]"] = filter; + } + + return getRequest( + createAction(REQUEST_SPONSOR_CART), + createAction(RECEIVE_SPONSOR_CART), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/carts/current`, + customErrorHandler, + { term, summitTZ } + )(params)(dispatch) + .catch((err) => { + console.error(err); + }) + .finally(() => { + dispatch(stopLoading()); + }); + }; + + +export const deleteSponsorCartForm = + (formId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { + entity: { id: sponsorId } + } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + dispatch(startLoading()); + + return deleteRequest( + null, + createAction(SPONSOR_CART_FORM_DELETED)({ formId }), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}`, + null, + snackbarErrorHandler + )(params)(dispatch) + .then(() => { + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_forms.form_delete_success") + }) + ); + }) + .finally(() => { + dispatch(stopLoading()); + }); + }; \ No newline at end of file diff --git a/src/components/mui/table/extra-rows/NotesRow.jsx b/src/components/mui/table/extra-rows/NotesRow.jsx new file mode 100644 index 000000000..6b4b61394 --- /dev/null +++ b/src/components/mui/table/extra-rows/NotesRow.jsx @@ -0,0 +1,16 @@ +import TableCell from "@mui/material/TableCell"; +import TableRow from "@mui/material/TableRow"; +import * as React from "react"; +import { Typography } from "@mui/material"; + +const NotesRow = ({ colCount, note }) => ( + + + + {note} + + + + ); + +export default NotesRow; diff --git a/src/components/mui/table/extra-rows/TotalRow.jsx b/src/components/mui/table/extra-rows/TotalRow.jsx new file mode 100644 index 000000000..2c1fa7d64 --- /dev/null +++ b/src/components/mui/table/extra-rows/TotalRow.jsx @@ -0,0 +1,32 @@ +import TableCell from "@mui/material/TableCell"; +import TableRow from "@mui/material/TableRow"; +import * as React from "react"; +import T from "i18n-react/dist/i18n-react"; + +const TotalRow = ({ columns, targetCol, total, trailing = 0 }) => { + return ( + + {columns.map((col, i) => { + if (i === 0) + return ( + + {T.translate("mui_table.total")} + + ); + if (col.columnKey === targetCol) + return ( + + {total} + + ); + return ; + })} + {[...Array(trailing)].map((_, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ); +}; + +export default TotalRow; diff --git a/src/components/mui/table/extra-rows/index.js b/src/components/mui/table/extra-rows/index.js new file mode 100644 index 000000000..6e7961237 --- /dev/null +++ b/src/components/mui/table/extra-rows/index.js @@ -0,0 +1,2 @@ +export { default as TotalRow } from "./TotalRow"; +export { default as NotesRow } from "./NotesRow"; diff --git a/src/components/mui/table/mui-table.js b/src/components/mui/table/mui-table.js index dc33d4e89..5f8b9d55b 100644 --- a/src/components/mui/table/mui-table.js +++ b/src/components/mui/table/mui-table.js @@ -3,9 +3,9 @@ import T from "i18n-react/dist/i18n-react"; import { isBoolean } from "lodash"; import { Box, + Button, IconButton, Paper, - Button, Table, TableBody, TableCell, @@ -31,6 +31,7 @@ import styles from "./mui-table.module.less"; const MuiTable = ({ columns = [], data = [], + children, totalRows, perPage, currentPage, @@ -253,6 +254,8 @@ const MuiTable = ({ )} ))} + {/* Here we inject extra rows passed as children */} + {children} {data.length === 0 && ( @@ -265,28 +268,30 @@ const MuiTable = ({ {/* PAGINATION */} - + {perPage && currentPage && ( + + )} ); diff --git a/src/i18n/en.json b/src/i18n/en.json index 7384e4d74..eae6ecea1 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2434,6 +2434,19 @@ "unarchived": "Form successfully unarchived." } }, + "cart_tab": { + "new_form": "New Form", + "forms": "forms", + "code": "Code", + "name": "Name", + "add_ons": "Add-ons", + "discount": "Discount", + "amount": "Amount", + "add_form": "Add Form", + "no_cart": "No cart found.", + "pay_cc": "pay with credit card or ach", + "pay_invoice": "pay with invoice" + }, "placeholders": { "select_sponsorship": "Select a Sponsorship", "sponsorship_type": "Start typing to choose a Tier...", @@ -3762,7 +3775,8 @@ "no_items": "No items found.", "rows_per_page": "Rows per page", "sorted_desc": "sorted descending", - "sorted_asc": "sorted ascending" + "sorted_asc": "sorted ascending", + "total": "Total" }, "event_rsvp_list": { "name": "Name", diff --git a/src/pages/sponsors/edit-sponsor-page.js b/src/pages/sponsors/edit-sponsor-page.js index 5f3b6e214..a69defbb8 100644 --- a/src/pages/sponsors/edit-sponsor-page.js +++ b/src/pages/sponsors/edit-sponsor-page.js @@ -42,6 +42,7 @@ import SponsorGeneralForm from "../../components/forms/sponsor-general-form/inde import SponsorUsersListPerSponsorPage from "./sponsor-users-list-per-sponsor"; import SponsorFormsTab from "./sponsor-forms-tab"; import SponsorBadgeScans from "./sponsor-badge-scans"; +import SponsorCartTab from "./sponsor-cart-tab"; const CustomTabPanel = (props) => { const { children, value, index, ...other } = props; @@ -127,7 +128,7 @@ const EditSponsorPage = (props) => { return ( - + {entity.company?.name} @@ -185,6 +186,9 @@ const EditSponsorPage = (props) => { + + + diff --git a/src/pages/sponsors/sponsor-cart-tab/index.js b/src/pages/sponsors/sponsor-cart-tab/index.js new file mode 100644 index 000000000..4eaf59046 --- /dev/null +++ b/src/pages/sponsors/sponsor-cart-tab/index.js @@ -0,0 +1,231 @@ +/** + * Copyright 2024 OpenStack Foundation + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * */ + +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import T from "i18n-react/dist/i18n-react"; +import { + Box, + Button, + Grid2, + IconButton, + Paper, + Typography +} from "@mui/material"; +import LockOpenIcon from "@mui/icons-material/LockOpen"; +import LockClosedIcon from "@mui/icons-material/Lock"; +import AddIcon from "@mui/icons-material/Add"; +import { + deleteSponsorCartForm, + getSponsorCart +} from "../../../actions/sponsor-cart-actions"; +import SearchInput from "../../../components/mui/search-input"; +import { TotalRow } from "../../../components/mui/table/extra-rows"; +import MuiTable from "../../../components/mui/table/mui-table"; + +const SponsorCartTab = ({ + cart, + term, + sponsor, + summitId, + getSponsorCart, + deleteSponsorCartForm +}) => { + const [openPopup, setOpenPopup] = useState(null); + const [formEdit, setFormEdit] = useState(null); + + useEffect(() => { + getSponsorCart(); + }, []); + + const handleSearch = (searchTerm) => { + getSponsorCart(searchTerm); + }; + + const handleManageItems = (item) => { + console.log("MANAGE ITEMS : ", item); + }; + + const handleEdit = (item) => { + setFormEdit(item); + }; + + const handleDelete = (itemId) => { + deleteSponsorCartForm(itemId); + }; + + const handleLock = (item) => { + console.log("LOCK : ", item); + }; + + const handlePayCreditCard = () => { + console.log("PAY CREDIT CARD"); + }; + + const handlePayInvoice = () => { + console.log("PAY INVOICE"); + }; + + const tableColumns = [ + { + columnKey: "code", + header: T.translate("edit_sponsor.cart_tab.code") + }, + { + columnKey: "name", + header: T.translate("edit_sponsor.cart_tab.name") + }, + { + columnKey: "allowed_add_ons", + header: T.translate("edit_sponsor.cart_tab.add_ons"), + render: (row) => + row.allowed_add_ons?.length > 0 + ? row.allowed_add_ons.map((a) => `${a.type} ${a.name}`).join(", ") + : "None" + }, + { + columnKey: "manage_items", + header: "", + width: 100, + align: "center", + render: (row) => ( + + ) + }, + { + columnKey: "discount", + header: T.translate("edit_sponsor.cart_tab.discount") + }, + { + columnKey: "amount", + header: T.translate("edit_sponsor.cart_tab.amount") + }, + { + columnKey: "lock", + header: "", + render: (row) => ( + handleLock(row)}> + {row.is_locked ? ( + + ) : ( + + )} + + ) + } + ]; + + return ( + + + + {cart && ( + {cart?.forms.length} forms in Cart + )} + + + + + + + + + {!cart && ( + + {T.translate("edit_sponsor.cart_tab.no_cart")} + + )} + {!!cart && ( + + + + + + + + + + )} + + ); +}; + +const mapStateToProps = ({ sponsorPageCartListState }) => ({ + ...sponsorPageCartListState +}); + +export default connect(mapStateToProps, { + getSponsorCart, + deleteSponsorCartForm +})(SponsorCartTab); diff --git a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js new file mode 100644 index 000000000..90db64212 --- /dev/null +++ b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js @@ -0,0 +1,81 @@ +/** + * 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 { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; +import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; +import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; +import { + REQUEST_SPONSOR_CART, + RECEIVE_SPONSOR_CART, + SPONSOR_CART_FORM_DELETED +} from "../../actions/sponsor-cart-actions"; +import { centsToDollar } from "../../utils/methods"; + +const DEFAULT_STATE = { + cart: null, + term: "", + summitTZ: "" +}; + +const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { + const { type, payload } = action; + + switch (type) { + case SET_CURRENT_SUMMIT: + case LOGOUT_USER: { + return DEFAULT_STATE; + } + case REQUEST_SPONSOR_CART: { + const { term, summitTZ } = payload; + + return { + ...state, + cart: null, + term, + summitTZ + }; + } + case RECEIVE_SPONSOR_CART: { + const cart = payload.response; + cart.forms = cart.forms.map((form) => ({ + ...form, + amount: centsToDollar(form.net_amount), + discount: centsToDollar(form.discount_amount) + })); + cart.total = centsToDollar(cart.net_amount); + + return { + ...state, + cart + }; + } + case SPONSOR_CART_FORM_DELETED: { + const { formId } = payload; + const forms = state.cart.forms.filter( + (form) => form.id !== formId + ); + + return { + ...state, + cart: { + ...state.cart, + forms + } + }; + } + default: + return state; + } +}; + +export default sponsorPageCartListReducer; diff --git a/src/store.js b/src/store.js index 64762f8ac..0d224d75f 100644 --- a/src/store.js +++ b/src/store.js @@ -164,6 +164,7 @@ import eventRSVPInvitationListReducer from "./reducers/rsvps/event-rsvp-invitati import eventRSVPReducer from "./reducers/events/event-rsvp-reducer.js"; import sponsorPageFormsListReducer from "./reducers/sponsors/sponsor-page-forms-list-reducer.js"; import sponsorCustomizedFormReducer from "./reducers/sponsors/sponsor-customized-form-reducer.js"; +import sponsorPageCartListReducer from "./reducers/sponsors/sponsor-page-cart-list-reducer"; // default: localStorage if web, AsyncStorage if react-native @@ -249,6 +250,7 @@ const reducers = persistCombineReducers(config, { sponsorFormItemsListState: sponsorFormItemsListReducer, sponsorUsersListState: sponsorUsersListReducer, sponsorPageFormsListState: sponsorPageFormsListReducer, + sponsorPageCartListState: sponsorPageCartListReducer, sponsorCustomizedFormState: sponsorCustomizedFormReducer, currentSponsorPromocodeListState: sponsorPromocodeListReducer, currentSponsorExtraQuestionState: sponsorExtraQuestionReducer, diff --git a/src/utils/constants.js b/src/utils/constants.js index a6d2d7139..2693c9e06 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -223,3 +223,5 @@ export const ROOM_OCCUPANCY_OPTIONS = [ ]; export const BADGE_QR_MINIMUM_EXPECTED_FIELDS = 3; + +export const CENTS_FOR_DOLLAR = 1000; \ No newline at end of file diff --git a/src/utils/methods.js b/src/utils/methods.js index 4eca84e11..7c01c7031 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -21,17 +21,19 @@ import Swal from "sweetalert2"; import * as Sentry from "@sentry/react"; import T from "i18n-react/dist/i18n-react"; import { + BADGE_QR_MINIMUM_EXPECTED_FIELDS, + CENTS_FOR_DOLLAR, + DECIMAL_DIGITS, ERROR_CODE_401, ERROR_CODE_403, ERROR_CODE_412, ERROR_CODE_500, HEX_RADIX, - MILLISECONDS_TO_SECONDS, - ONE_MINUTE, INT_BASE, - OR_FILTER, MARKETING_SETTING_TYPE_HEX_COLOR, - BADGE_QR_MINIMUM_EXPECTED_FIELDS + MILLISECONDS_TO_SECONDS, + ONE_MINUTE, + OR_FILTER } from "./constants"; const DAY_IN_SECONDS = 86400; // 86400 seconds per day @@ -530,3 +532,6 @@ export const formatBadgeQR = (code, summit) => { return null; }; + +export const centsToDollar = (cents) => + `$${(cents / CENTS_FOR_DOLLAR).toFixed(DECIMAL_DIGITS)}`; From 6410e1ec8e724ad5c1e9176efdbbfa7407c8b9eb Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 9 Jan 2026 11:15:56 -0300 Subject: [PATCH 2/6] fix: remove centsToDollar --- .../sponsors/sponsor-page-cart-list-reducer.js | 13 +++++-------- src/utils/constants.js | 2 -- src/utils/methods.js | 5 ----- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js index 90db64212..93c012b85 100644 --- a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js @@ -12,14 +12,13 @@ * */ import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions"; -import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; +import { amountFromCents } from "openstack-uicore-foundation/lib/utils/money"; import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { REQUEST_SPONSOR_CART, RECEIVE_SPONSOR_CART, SPONSOR_CART_FORM_DELETED } from "../../actions/sponsor-cart-actions"; -import { centsToDollar } from "../../utils/methods"; const DEFAULT_STATE = { cart: null, @@ -49,10 +48,10 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { const cart = payload.response; cart.forms = cart.forms.map((form) => ({ ...form, - amount: centsToDollar(form.net_amount), - discount: centsToDollar(form.discount_amount) + amount: amountFromCents(form.net_amount), + discount: amountFromCents(form.discount_amount) })); - cart.total = centsToDollar(cart.net_amount); + cart.total = amountFromCents(cart.net_amount); return { ...state, @@ -61,9 +60,7 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { } case SPONSOR_CART_FORM_DELETED: { const { formId } = payload; - const forms = state.cart.forms.filter( - (form) => form.id !== formId - ); + const forms = state.cart.forms.filter((form) => form.id !== formId); return { ...state, diff --git a/src/utils/constants.js b/src/utils/constants.js index 2693c9e06..a6d2d7139 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -223,5 +223,3 @@ export const ROOM_OCCUPANCY_OPTIONS = [ ]; export const BADGE_QR_MINIMUM_EXPECTED_FIELDS = 3; - -export const CENTS_FOR_DOLLAR = 1000; \ No newline at end of file diff --git a/src/utils/methods.js b/src/utils/methods.js index 7c01c7031..a558014ca 100644 --- a/src/utils/methods.js +++ b/src/utils/methods.js @@ -22,8 +22,6 @@ import * as Sentry from "@sentry/react"; import T from "i18n-react/dist/i18n-react"; import { BADGE_QR_MINIMUM_EXPECTED_FIELDS, - CENTS_FOR_DOLLAR, - DECIMAL_DIGITS, ERROR_CODE_401, ERROR_CODE_403, ERROR_CODE_412, @@ -532,6 +530,3 @@ export const formatBadgeQR = (code, summit) => { return null; }; - -export const centsToDollar = (cents) => - `$${(cents / CENTS_FOR_DOLLAR).toFixed(DECIMAL_DIGITS)}`; From 44050b0b6f9debcbdcaa7dd1ba2223684b3a1519 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 9 Jan 2026 11:19:36 -0300 Subject: [PATCH 3/6] fix: translate string --- src/i18n/en.json | 1 + src/pages/sponsors/sponsor-cart-tab/index.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/en.json b/src/i18n/en.json index eae6ecea1..3ae951780 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -2442,6 +2442,7 @@ "add_ons": "Add-ons", "discount": "Discount", "amount": "Amount", + "manage_items": "Manage Items", "add_form": "Add Form", "no_cart": "No cart found.", "pay_cc": "pay with credit card or ach", diff --git a/src/pages/sponsors/sponsor-cart-tab/index.js b/src/pages/sponsors/sponsor-cart-tab/index.js index 4eaf59046..4d1d9b564 100644 --- a/src/pages/sponsors/sponsor-cart-tab/index.js +++ b/src/pages/sponsors/sponsor-cart-tab/index.js @@ -105,7 +105,7 @@ const SponsorCartTab = ({ size="small" onClick={() => handleManageItems(row)} > - Manage Items + {T.translate("edit_sponsor.cart_tab.manage_items")} ) }, From bb9b41dc72872df725e88f0c99b1b360b119e86b Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Fri, 9 Jan 2026 12:30:49 -0300 Subject: [PATCH 4/6] fix: remove unnecesary scopes --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 370fa00a8..9c466c3b5 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ 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_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 my-cart/read my-cart/write +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 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" EMAIL_SCOPES="clients/read templates/read templates/write emails/read" FILE_UPLOAD_SCOPES="files/upload" From ef179912cd9dede49763c51a20d28fa3c9018fb1 Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Mon, 12 Jan 2026 11:05:49 -0300 Subject: [PATCH 5/6] feat: lock/unlock form --- src/actions/sponsor-cart-actions.js | 53 +++++++++++++++++++ src/pages/sponsors/sponsor-cart-tab/index.js | 20 +++++-- .../sponsor-page-cart-list-reducer.js | 21 +++++++- 3 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index cbbf74a5a..7f4a9d96c 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -16,6 +16,7 @@ import { createAction, getRequest, deleteRequest, + putRequest, startLoading, stopLoading } from "openstack-uicore-foundation/lib/utils/actions"; @@ -28,6 +29,7 @@ import { ERROR_CODE_404 } from "../utils/constants"; export const REQUEST_SPONSOR_CART = "REQUEST_SPONSOR_CART"; export const RECEIVE_SPONSOR_CART = "RECEIVE_SPONSOR_CART"; export const SPONSOR_CART_FORM_DELETED = "SPONSOR_CART_FORM_DELETED"; +export const SPONSOR_CART_FORM_LOCKED = "SPONSOR_CART_FORM_LOCKED"; const customErrorHandler = (err, res) => (dispatch, state) => { const code = err.status; @@ -113,4 +115,55 @@ export const deleteSponsorCartForm = .finally(() => { dispatch(stopLoading()); }); + }; + + +export const lockSponsorCartForm = (formId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + + const accessToken = await getAccessTokenSafely(); + + const params = { + access_token: accessToken + }; + + dispatch(startLoading()); + + putRequest( + null, + createAction(SPONSOR_CART_FORM_LOCKED)({ formId, locked: true }), + `${window.SPONSOR_USERS_API_URL}/api/v1/shows/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/forms/${formId}/lock`, + {}, + snackbarErrorHandler + )(params)(dispatch) + .catch(console.log) // need to catch promise reject + .finally(() => { + dispatch(stopLoading()); + }); +}; + + +export const unlockSponsorCartForm = + (formId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; + + dispatch(startLoading()); + + return deleteRequest( + null, + createAction(SPONSOR_CART_FORM_LOCKED)({ formId, locked: false }), + `${window.SPONSOR_USERS_API_URL}/api/v1/shows/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/forms/${formId}/lock`, + null, + snackbarErrorHandler + )(params)(dispatch) + .catch(console.log) // need to catch promise reject + .finally(() => { + dispatch(stopLoading()); + }); }; \ No newline at end of file diff --git a/src/pages/sponsors/sponsor-cart-tab/index.js b/src/pages/sponsors/sponsor-cart-tab/index.js index 4d1d9b564..c41d0f587 100644 --- a/src/pages/sponsors/sponsor-cart-tab/index.js +++ b/src/pages/sponsors/sponsor-cart-tab/index.js @@ -27,7 +27,9 @@ import LockClosedIcon from "@mui/icons-material/Lock"; import AddIcon from "@mui/icons-material/Add"; import { deleteSponsorCartForm, - getSponsorCart + getSponsorCart, + lockSponsorCartForm, + unlockSponsorCartForm } from "../../../actions/sponsor-cart-actions"; import SearchInput from "../../../components/mui/search-input"; import { TotalRow } from "../../../components/mui/table/extra-rows"; @@ -39,7 +41,9 @@ const SponsorCartTab = ({ sponsor, summitId, getSponsorCart, - deleteSponsorCartForm + deleteSponsorCartForm, + lockSponsorCartForm, + unlockSponsorCartForm }) => { const [openPopup, setOpenPopup] = useState(null); const [formEdit, setFormEdit] = useState(null); @@ -64,8 +68,12 @@ const SponsorCartTab = ({ deleteSponsorCartForm(itemId); }; - const handleLock = (item) => { - console.log("LOCK : ", item); + const handleLock = (form) => { + if (form.is_locked) { + unlockSponsorCartForm(form.form_id); + } else { + lockSponsorCartForm(form.form_id); + } }; const handlePayCreditCard = () => { @@ -227,5 +235,7 @@ const mapStateToProps = ({ sponsorPageCartListState }) => ({ export default connect(mapStateToProps, { getSponsorCart, - deleteSponsorCartForm + deleteSponsorCartForm, + lockSponsorCartForm, + unlockSponsorCartForm })(SponsorCartTab); diff --git a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js index 93c012b85..7d12f3429 100644 --- a/src/reducers/sponsors/sponsor-page-cart-list-reducer.js +++ b/src/reducers/sponsors/sponsor-page-cart-list-reducer.js @@ -17,7 +17,8 @@ import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions"; import { REQUEST_SPONSOR_CART, RECEIVE_SPONSOR_CART, - SPONSOR_CART_FORM_DELETED + SPONSOR_CART_FORM_DELETED, + SPONSOR_CART_FORM_LOCKED } from "../../actions/sponsor-cart-actions"; const DEFAULT_STATE = { @@ -70,6 +71,24 @@ const sponsorPageCartListReducer = (state = DEFAULT_STATE, action) => { } }; } + case SPONSOR_CART_FORM_LOCKED: { + const { formId, locked } = payload; + + const forms = state.cart.forms.map((form) => { + if (form.form_id === formId) { + return {...form, locked}; + } + return form; + }); + + return { + ...state, + cart: { + ...state.cart, + forms + } + }; + } default: return state; } From 19202f796bed42c8e6720cac7cd3440995e57d5e Mon Sep 17 00:00:00 2001 From: Santiago Palenque Date: Mon, 12 Jan 2026 11:14:21 -0300 Subject: [PATCH 6/6] feat: delete cart form --- src/actions/sponsor-cart-actions.js | 100 +++++++++++++--------------- 1 file changed, 48 insertions(+), 52 deletions(-) diff --git a/src/actions/sponsor-cart-actions.js b/src/actions/sponsor-cart-actions.js index 7f4a9d96c..3d5cf0cd4 100644 --- a/src/actions/sponsor-cart-actions.js +++ b/src/actions/sponsor-cart-actions.js @@ -84,39 +84,37 @@ export const getSponsorCart = }); }; +export const deleteSponsorCartForm = (formId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { + entity: { id: sponsorId } + } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; -export const deleteSponsorCartForm = - (formId) => async (dispatch, getState) => { - const { currentSummitState, currentSponsorState } = getState(); - const { currentSummit } = currentSummitState; - const { - entity: { id: sponsorId } - } = currentSponsorState; - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - - dispatch(startLoading()); - - return deleteRequest( - null, - createAction(SPONSOR_CART_FORM_DELETED)({ formId }), - `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/sponsor-forms/${formId}`, - null, - snackbarErrorHandler - )(params)(dispatch) - .then(() => { - dispatch( - snackbarSuccessHandler({ - title: T.translate("general.success"), - html: T.translate("sponsor_forms.form_delete_success") - }) - ); - }) - .finally(() => { - dispatch(stopLoading()); - }); - }; + dispatch(startLoading()); + return deleteRequest( + null, + createAction(SPONSOR_CART_FORM_DELETED)({ formId }), + `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/carts/current/forms/${formId}`, + null, + snackbarErrorHandler + )(params)(dispatch) + .then(() => { + getSponsorCart()(dispatch, getState); + dispatch( + snackbarSuccessHandler({ + title: T.translate("general.success"), + html: T.translate("sponsor_forms.form_delete_success") + }) + ); + }) + .finally(() => { + dispatch(stopLoading()); + }); +}; export const lockSponsorCartForm = (formId) => async (dispatch, getState) => { const { currentSummitState, currentSponsorState } = getState(); @@ -144,26 +142,24 @@ export const lockSponsorCartForm = (formId) => async (dispatch, getState) => { }); }; +export const unlockSponsorCartForm = (formId) => async (dispatch, getState) => { + const { currentSummitState, currentSponsorState } = getState(); + const { currentSummit } = currentSummitState; + const { entity: sponsor } = currentSponsorState; + const accessToken = await getAccessTokenSafely(); + const params = { access_token: accessToken }; -export const unlockSponsorCartForm = - (formId) => async (dispatch, getState) => { - const { currentSummitState, currentSponsorState } = getState(); - const { currentSummit } = currentSummitState; - const { entity: sponsor } = currentSponsorState; - const accessToken = await getAccessTokenSafely(); - const params = { access_token: accessToken }; - - dispatch(startLoading()); + dispatch(startLoading()); - return deleteRequest( - null, - createAction(SPONSOR_CART_FORM_LOCKED)({ formId, locked: false }), - `${window.SPONSOR_USERS_API_URL}/api/v1/shows/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/forms/${formId}/lock`, - null, - snackbarErrorHandler - )(params)(dispatch) - .catch(console.log) // need to catch promise reject - .finally(() => { - dispatch(stopLoading()); - }); - }; \ No newline at end of file + return deleteRequest( + null, + createAction(SPONSOR_CART_FORM_LOCKED)({ formId, locked: false }), + `${window.SPONSOR_USERS_API_URL}/api/v1/shows/${currentSummit.id}/sponsors/${sponsor.id}/carts/current/forms/${formId}/lock`, + null, + snackbarErrorHandler + )(params)(dispatch) + .catch(console.log) // need to catch promise reject + .finally(() => { + dispatch(stopLoading()); + }); +};