diff --git a/package.json b/package.json
index 67ffb6622..af6cbc9f7 100644
--- a/package.json
+++ b/package.json
@@ -92,7 +92,7 @@
"moment": "^2.29.1",
"moment-duration-format": "^2.3.2",
"moment-timezone": "^0.5.33",
- "openstack-uicore-foundation": "5.0.22",
+ "openstack-uicore-foundation": "5.0.29-beta.0",
"p-limit": "^6.1.0",
"path-browserify": "^1.0.1",
"postcss-loader": "^6.2.1",
diff --git a/src/actions/sponsor-purchases-actions.js b/src/actions/sponsor-purchases-actions.js
index 2b38f833d..6d21e082f 100644
--- a/src/actions/sponsor-purchases-actions.js
+++ b/src/actions/sponsor-purchases-actions.js
@@ -19,7 +19,8 @@ import {
postRequest,
putRequest,
startLoading,
- stopLoading
+ stopLoading,
+ getCSV
} from "openstack-uicore-foundation/lib/utils/actions";
import T from "i18n-react/dist/i18n-react";
import { escapeFilterValue, getAccessTokenSafely } from "../utils/methods";
@@ -31,6 +32,8 @@ import {
} from "../utils/constants";
import { snackbarErrorHandler, snackbarSuccessHandler } from "./base-actions";
+export const REQUEST_ALL_SPONSOR_PURCHASES = "REQUEST_ALL_SPONSOR_PURCHASES";
+export const RECEIVE_ALL_SPONSOR_PURCHASES = "RECEIVE_ALL_SPONSOR_PURCHASES";
export const REQUEST_SPONSOR_PURCHASES = "REQUEST_SPONSOR_PURCHASES";
export const RECEIVE_SPONSOR_PURCHASES = "RECEIVE_SPONSOR_PURCHASES";
export const SPONSOR_PURCHASE_STATUS_UPDATED =
@@ -40,6 +43,127 @@ export const CLEAR_SPONSOR_ORDER = "CLEAR_SPONSOR_ORDER";
export const SPONSOR_CLIENT_ADDRESS_UPDATED = "SPONSOR_CLIENT_ADDRESS_UPDATED";
export const SPONSOR_CLIENT_UPDATED = "SPONSOR_CLIENT_UPDATED";
+export const getAllSponsorPurchases =
+ (
+ term = "",
+ page = DEFAULT_CURRENT_PAGE,
+ perPage = DEFAULT_PER_PAGE,
+ order = "created",
+ orderDir = -1
+ ) =>
+ 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(
+ `number==${escapedTerm},sponsor_company_name=@${escapedTerm},purchased_by_email=@${escapedTerm},purchased_by_full_name=@${escapedTerm}`
+ );
+ }
+
+ const params = {
+ page,
+ per_page: perPage,
+ access_token: accessToken,
+ expand: "sponsor",
+ relations: "sponsor",
+ fields:
+ "id,number,payment_id,purchased_date,sponsor.id,sponsor.company_name,payment_method,status,net_amount"
+ };
+
+ if (filter.length > 0) {
+ params["filter[]"] = filter;
+ }
+
+ // order
+ if (order != null && orderDir != null) {
+ const orderDirSign = orderDir === 1 ? "" : "-";
+ switch (order) {
+ case "purchased":
+ params.order = `${orderDirSign}created`;
+ break;
+ case "amount":
+ params.order = `${orderDirSign}net_amount`;
+ break;
+ case "sponsor_name":
+ params.order = `${orderDirSign}sponsor_company_name`;
+ break;
+ default:
+ params.order = `${orderDirSign}${order}`;
+ }
+ }
+
+ return getRequest(
+ createAction(REQUEST_ALL_SPONSOR_PURCHASES),
+ createAction(RECEIVE_ALL_SPONSOR_PURCHASES),
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/purchases`,
+ authErrorHandler,
+ { order, orderDir, page, perPage, term }
+ )(params)(dispatch).finally(() => {
+ dispatch(stopLoading());
+ });
+ };
+
+export const exportAllSponsorPurchases =
+ (term = null, order, orderDir) =>
+ async (dispatch, getState) => {
+ const { currentSummitState } = getState();
+ const accessToken = await getAccessTokenSafely();
+ const { currentSummit } = currentSummitState;
+ const filter = [];
+ const filename = `${currentSummit.id}-purchases.csv`;
+
+ if (term) {
+ const escapedTerm = escapeFilterValue(term);
+ filter.push(
+ `number==${escapedTerm},sponsor_company_name=@${escapedTerm},purchased_by_email=@${escapedTerm},purchased_by_full_name=@${escapedTerm}`
+ );
+ }
+
+ const params = {
+ access_token: accessToken,
+ expand: "sponsor",
+ relations: "sponsor",
+ fields:
+ "number,payment_id,purchased_date,sponsor.id,sponsor.company_name,payment_method,status,net_amount"
+ };
+
+ if (filter.length > 0) {
+ params["filter[]"] = filter;
+ }
+
+ // order
+ if (order != null && orderDir != null) {
+ const orderDirSign = orderDir === 1 ? "" : "-";
+ switch (order) {
+ case "purchased":
+ params.order = `${orderDirSign}created`;
+ break;
+ case "amount":
+ params.order = `${orderDirSign}net_amount`;
+ break;
+ case "sponsor_name":
+ params.order = `${orderDirSign}sponsor_company_name`;
+ break;
+ default:
+ params.order = `${orderDirSign}${order}`;
+ }
+ }
+
+ dispatch(
+ getCSV(
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/purchases/csv`,
+ params,
+ filename
+ )
+ );
+ };
+
export const getSponsorPurchases =
(
term = "",
@@ -82,7 +206,7 @@ export const getSponsorPurchases =
params.order = `${orderDirSign}created`;
break;
case "amount":
- params.order = `${orderDirSign}raw_amount`;
+ params.order = `${orderDirSign}net_amount`;
break;
default:
params.order = `${orderDirSign}${order}`;
@@ -101,10 +225,9 @@ export const getSponsorPurchases =
};
export const approveSponsorPurchase =
- (paymentId) => async (dispatch, getState) => {
- const { currentSummitState, currentSponsorState } = getState();
+ (sponsorId, paymentId) => async (dispatch, getState) => {
+ const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
- const { entity: sponsor } = currentSponsorState;
const accessToken = await getAccessTokenSafely();
const params = {
@@ -119,7 +242,7 @@ export const approveSponsorPurchase =
paymentId,
status: PURCHASE_STATUS.PAID
}),
- `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentId}/approve`,
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/payments/${paymentId}/approve`,
{},
snackbarErrorHandler
)(params)(dispatch)
@@ -138,10 +261,9 @@ export const approveSponsorPurchase =
};
export const rejectSponsorPurchase =
- (paymentId) => async (dispatch, getState) => {
- const { currentSummitState, currentSponsorState } = getState();
+ (sponsorId, paymentId) => async (dispatch, getState) => {
+ const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
- const { entity: sponsor } = currentSponsorState;
const accessToken = await getAccessTokenSafely();
const params = {
@@ -156,7 +278,7 @@ export const rejectSponsorPurchase =
paymentId,
status: PURCHASE_STATUS.CANCELLED
}),
- `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsor.id}/payments/${paymentId}/cancel`,
+ `${window.PURCHASES_API_URL}/api/v1/summits/${currentSummit.id}/sponsors/${sponsorId}/payments/${paymentId}/cancel`,
null,
snackbarErrorHandler
)(params)(dispatch)
diff --git a/src/components/menu/menu-definition.js b/src/components/menu/menu-definition.js
index d853d6d58..0baaa4985 100644
--- a/src/components/menu/menu-definition.js
+++ b/src/components/menu/menu-definition.js
@@ -218,6 +218,11 @@ export const getSummitItems = (summitId) => [
linkUrl: `summits/${summitId}/sponsors/pages`,
accessRoute: "admin-sponsors"
},
+ {
+ name: "sponsor_purchases",
+ linkUrl: `summits/${summitId}/sponsors/purchases`,
+ accessRoute: "admin-sponsors"
+ },
{
name: "sponsorship_list",
linkUrl: `summits/${summitId}/sponsorships`,
diff --git a/src/components/mui/RefundForm/index.jsx b/src/components/mui/RefundForm/index.jsx
index b545207e3..ca586e031 100644
--- a/src/components/mui/RefundForm/index.jsx
+++ b/src/components/mui/RefundForm/index.jsx
@@ -19,8 +19,9 @@ import { Box, Button, Grid2 } from "@mui/material";
import MuiFormikTextField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/textfield";
import MuiFormikPriceField from "openstack-uicore-foundation/lib/components/mui/formik-inputs/price-field";
import InfoNote from "openstack-uicore-foundation/lib/components/mui/info-note";
+import CustomAlert from "openstack-uicore-foundation/lib/components/mui/custom-alert";
-const RefundForm = ({ onSubmit }) => {
+const RefundForm = ({ onSubmit, disabled = false }) => {
const formik = useFormik({
initialValues: {
reason: "",
@@ -56,6 +57,7 @@ const RefundForm = ({ onSubmit }) => {
fullWidth
size="small"
label={T.translate("refund_form.reason")}
+ disabled={disabled}
/>
@@ -65,6 +67,7 @@ const RefundForm = ({ onSubmit }) => {
size="small"
inCents
label={T.translate("refund_form.amount")}
+ disabled={disabled}
/>
@@ -74,13 +77,23 @@ const RefundForm = ({ onSubmit }) => {
color="primary"
fullWidth
size="small"
+ disabled={disabled}
>
{T.translate("refund_form.queue_refund")}
-
+
+ {disabled ? (
+
+ ) : (
+
+ )}
+
);
};
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 37f8d0580..6d56f6adf 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -90,6 +90,7 @@
"to": "To",
"from": "From",
"placeholders": {
+ "search": "Search...",
"search_speakers": "Search Speakers by Name, Email, Speaker Id or Member Id",
"select_acceptance_criteria": "Select acceptance criteria",
"select_invitation_status": "Select status"
@@ -186,6 +187,7 @@
"sponsor_list": "Sponsor List",
"sponsor_forms": "Forms",
"sponsor_pages": "Pages",
+ "sponsor_purchases": "Purchases",
"sponsorship_list": "Tiers",
"sponsor_users": "Users",
"sponsors_promocodes": "Promo Codes",
@@ -2881,6 +2883,16 @@
}
}
},
+ "sponsor_show_purchases": {
+ "order": "Order",
+ "purchased": "Purchased",
+ "sponsor": "Sponsor",
+ "payment_method": "Payment Method",
+ "status": "Status",
+ "amount": "Amount",
+ "details": "Details",
+ "purchases": "Purchases"
+ },
"sponsor_users": {
"users": "Users",
"access_request": "access request",
@@ -4191,7 +4203,8 @@
"reason": "Reason for Refund",
"amount": "Amount",
"queue_refund": "Queue refund",
- "info": "If the original payment was made via Stripe, the refund will be automatically queued and processed."
+ "info": "If the original payment was made via Stripe, the refund will be automatically queued and processed.",
+ "only_online_payments": "Only online payments are eligible for refunds"
},
"client_card": {
"title": "Client & Address Details",
diff --git a/src/layouts/sponsor-id-layout.js b/src/layouts/sponsor-id-layout.js
index 991e6bf50..31211bf80 100644
--- a/src/layouts/sponsor-id-layout.js
+++ b/src/layouts/sponsor-id-layout.js
@@ -1,10 +1,24 @@
-import React, { Suspense } from "react";
+/**
+ * Copyright 2026 OpenStack Foundation
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * */
+
+import React, { Suspense, useEffect } from "react";
import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
import { Breadcrumb } from "react-breadcrumbs";
-import { Switch, Route } from "react-router-dom";
+import { Route, Switch } from "react-router-dom";
import AjaxLoader from "openstack-uicore-foundation/lib/components/ajaxloader";
import { getSponsor, resetSponsorForm } from "../actions/sponsor-actions";
+import { INT_BASE } from "../utils/constants";
const SponsorPage = React.lazy(() => import("../pages/sponsors/sponsor-page"));
const EditAdSponsorPage = React.lazy(() =>
@@ -21,149 +35,136 @@ const EditSponsorExtraQuestion = React.lazy(() =>
);
const NoMatchPage = React.lazy(() => import("../pages/no-match-page"));
-class SponsorIdLayout extends React.Component {
- constructor(props) {
- const sponsorId = props.match.params.sponsor_id;
- super(props);
+const SponsorIdLayout = ({
+ currentSponsor,
+ match,
+ resetSponsorForm,
+ getSponsor
+}) => {
+ const sponsorId = match.params.sponsor_id;
+ useEffect(() => {
if (!sponsorId) {
- props.resetSponsorForm();
+ resetSponsorForm();
} else {
- props.getSponsor(sponsorId);
- }
- }
-
- componentDidUpdate(prevProps) {
- const oldId = prevProps.match.params.sponsor_id;
- const newId = this.props.match.params.sponsor_id;
-
- if (oldId !== newId) {
- if (!newId) {
- this.props.resetSponsorForm();
- } else {
- this.props.getSponsor(newId);
- }
+ getSponsor(sponsorId);
}
- }
+ }, [sponsorId]);
- render() {
- const { match, currentSponsor } = this.props;
- const sponsorId = this.props.match.params.sponsor_id;
- const breadcrumb = currentSponsor.id
- ? currentSponsor.company?.name
- : T.translate("general.new");
+ if (!currentSponsor || parseInt(sponsorId, INT_BASE) !== currentSponsor.id)
+ return
;
- if (sponsorId && !currentSponsor.id) return ;
+ const breadcrumb = currentSponsor.id
+ ? currentSponsor.company?.name
+ : T.translate("general.new");
- return (
-
-
-
}>
-
- (
-
-
+
+ }>
+
+ (
+
+
+
+
+
+
+
+
+ )}
+ />
+ (
+
+
+
+
+
+
+
+
+ )}
+ />
+ (
+
+
+
+
-
-
-
-
-
-
- )}
- />
- (
-
-
-
-
-
-
-
-
- )}
- />
- (
-
-
+
+
+ )}
+ />
+ (
+
+
+
+
-
-
-
-
-
-
- )}
- />
- (
-
-
-
-
-
-
-
-
- )}
- />
-
-
-
-
-
- );
- }
-}
+
+
+
+ )}
+ />
+
+
+
+
+
+ );
+};
-const mapStateToProps = ({ currentSponsorState, currentSummitState }) => ({
- currentSponsor: currentSponsorState.entity,
- ...currentSummitState
+const mapStateToProps = ({ currentSponsorState }) => ({
+ currentSponsor: currentSponsorState.entity
});
export default connect(mapStateToProps, {
diff --git a/src/layouts/sponsor-layout.js b/src/layouts/sponsor-layout.js
index cba7da2e6..68dd29cd6 100644
--- a/src/layouts/sponsor-layout.js
+++ b/src/layouts/sponsor-layout.js
@@ -44,6 +44,9 @@ const SponsorUsersListPage = React.lazy(() =>
const ShowPagesListPage = React.lazy(() =>
import("../pages/sponsors/show-pages-list-page")
);
+const SponsorOrdersListPage = React.lazy(() =>
+ import("../pages/sponsors/show-purchase-list-page")
+);
const SponsorLayout = ({ match }) => (
@@ -85,20 +88,16 @@ const SponsorLayout = ({ match }) => (
/>
(
-
-
-
- )}
strict
exact
component={ShowPagesListPage}
/>
+
({
...overrides
});
-const renderWithConfirmDialog = (ui, options) => renderWithRedux(
+const renderWithConfirmDialog = (ui, options) =>
+ renderWithRedux(
<>
{ui}
@@ -89,15 +90,18 @@ describe("ShowPagesListPage", () => {
describe("Component", () => {
it("should render empty state when no pages exist", () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [],
- totalCount: 0
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [],
+ totalCount: 0
+ }
}
}
- });
+ );
expect(
screen.getByText("show_pages.no_sponsors_pages")
@@ -105,30 +109,36 @@ describe("ShowPagesListPage", () => {
});
it("should render table when pages exist", () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1), createShowPage(2)],
- totalCount: 2
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1), createShowPage(2)],
+ totalCount: 2
+ }
}
}
- });
+ );
expect(screen.getByText("Page 1")).toBeInTheDocument();
expect(screen.getByText("Page 2")).toBeInTheDocument();
});
it("should call getShowPage and open popup when edit is clicked", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1)],
- totalCount: 1
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1)],
+ totalCount: 1
+ }
}
}
- });
+ );
const editButton = screen.getByTestId("EditIcon").closest("button");
await act(async () => {
await userEvent.click(editButton);
@@ -144,15 +154,18 @@ describe("ShowPagesListPage", () => {
});
it("should refresh list after save", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1)],
- totalCount: 1
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1)],
+ totalCount: 1
+ }
}
}
- });
+ );
const editButton = screen.getByTestId("EditIcon").closest("button");
await act(async () => {
await userEvent.click(editButton);
@@ -177,19 +190,22 @@ describe("ShowPagesListPage", () => {
});
it("should call deleteShowPage and refresh list when delete is confirmed", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [
- createShowPage(1),
- createShowPage(2),
- createShowPage(3)
- ],
- totalCount: 3
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [
+ createShowPage(1),
+ createShowPage(2),
+ createShowPage(3)
+ ],
+ totalCount: 3
+ }
}
}
- });
+ );
const deleteButtons = screen.getAllByTestId("DeleteIcon");
const secondDeleteButton = deleteButtons[1].closest("button");
await act(async () => {
@@ -214,15 +230,18 @@ describe("ShowPagesListPage", () => {
});
it("should not call deleteShowPage when delete is cancelled", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1)],
- totalCount: 1
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1)],
+ totalCount: 1
+ }
}
}
- });
+ );
const deleteButton = screen.getByTestId("DeleteIcon").closest("button");
await act(async () => {
await userEvent.click(deleteButton);
@@ -242,15 +261,18 @@ describe("ShowPagesListPage", () => {
});
it("should call archiveShowPage for non-archived item", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1, { is_archived: false })],
- totalCount: 1
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1, { is_archived: false })],
+ totalCount: 1
+ }
}
}
- });
+ );
const archiveButton = screen.getByText("general.archive");
await act(async () => {
await userEvent.click(archiveButton);
@@ -259,15 +281,18 @@ describe("ShowPagesListPage", () => {
});
it("should call unarchiveShowPage for archived item", async () => {
- renderWithConfirmDialog(, {
- initialState: {
- showPagesListState: {
- ...showPagesListDefaultState,
- showPages: [createShowPage(1, { is_archived: true })],
- totalCount: 1
+ renderWithConfirmDialog(
+ ,
+ {
+ initialState: {
+ showPagesListState: {
+ ...showPagesListDefaultState,
+ showPages: [createShowPage(1, { is_archived: true })],
+ totalCount: 1
+ }
}
}
- });
+ );
const unarchiveButton = screen.getByText("general.unarchive");
await act(async () => {
await userEvent.click(unarchiveButton);
diff --git a/src/pages/sponsors/show-pages-list-page/index.js b/src/pages/sponsors/show-pages-list-page/index.js
index b4cbe22d7..450652e90 100644
--- a/src/pages/sponsors/show-pages-list-page/index.js
+++ b/src/pages/sponsors/show-pages-list-page/index.js
@@ -14,6 +14,7 @@
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
+import { Breadcrumb } from "react-breadcrumbs";
import {
Box,
Button,
@@ -26,13 +27,13 @@ import AddIcon from "@mui/icons-material/Add";
import MuiTable from "openstack-uicore-foundation/lib/components/mui/table";
import SearchInput from "openstack-uicore-foundation/lib/components/mui/search-input";
import {
- getShowPages,
archiveShowPage,
- unarchiveShowPage,
+ deleteShowPage,
getShowPage,
+ getShowPages,
+ resetShowPageForm,
saveShowPage,
- deleteShowPage,
- resetShowPageForm
+ unarchiveShowPage
} from "../../../actions/show-pages-actions";
import { getSummitSponsorshipTypes } from "../../../actions/summit-actions";
import CustomAlert from "../../../components/mui/custom-alert";
@@ -41,6 +42,7 @@ import PageTemplatePopup from "../../sponsors-global/page-templates/page-templat
import { DEFAULT_CURRENT_PAGE, MAX_PER_PAGE } from "../../../utils/constants";
const ShowPagesListPage = ({
+ match,
showPages,
currentPage,
perPage,
@@ -179,6 +181,14 @@ const ShowPagesListPage = ({
return (
+
+
+
{T.translate("show_pages.pages")}
{
+ useEffect(() => {
+ getAllSponsorPurchases();
+ }, []);
+
+ const handlePageChange = (page) => {
+ getAllSponsorPurchases(term, page, perPage, order, orderDir);
+ };
+
+ const handleSort = (key, dir) => {
+ getAllSponsorPurchases(term, currentPage, perPage, key, dir);
+ };
+
+ const handlePerPageChange = (newPerPage) => {
+ getAllSponsorPurchases(
+ term,
+ DEFAULT_CURRENT_PAGE,
+ newPerPage,
+ order,
+ orderDir
+ );
+ };
+
+ const handleExport = () => {
+ exportAllSponsorPurchases(term, order, orderDir);
+ };
+
+ const handleSearch = (searchTerm) => {
+ getAllSponsorPurchases(searchTerm);
+ };
+
+ const handleDetails = (item) => {
+ history.push(`${item.sponsor_id}/purchases/${item.id}`);
+ };
+
+ const handleMenu = (item) => {
+ console.log("MENU : ", item);
+ };
+
+ const handleStatusChange = (sponsorId, purchaseId, newStatus) => {
+ if (newStatus === PURCHASE_STATUS.PAID)
+ approveSponsorPurchase(sponsorId, purchaseId);
+ if (newStatus === PURCHASE_STATUS.CANCELLED)
+ rejectSponsorPurchase(sponsorId, purchaseId);
+ };
+
+ const tableColumns = [
+ {
+ columnKey: "number",
+ header: T.translate("sponsor_show_purchases.order"),
+ sortable: true
+ },
+ {
+ columnKey: "purchased",
+ header: T.translate("sponsor_show_purchases.purchased"),
+ width: 200,
+ sortable: true
+ },
+ {
+ columnKey: "sponsor_name",
+ header: T.translate("sponsor_show_purchases.sponsor"),
+ sortable: true
+ },
+ {
+ columnKey: "payment_method",
+ header: T.translate("sponsor_show_purchases.payment_method"),
+ sortable: true
+ },
+ {
+ columnKey: "status",
+ header: T.translate("sponsor_show_purchases.status"),
+ sortable: true,
+ render: (row) => {
+ if (
+ row.payment_method === PURCHASE_METHODS.INVOICE &&
+ row.status === PURCHASE_STATUS.PENDING
+ ) {
+ return (
+
+ );
+ }
+
+ return row.status;
+ }
+ },
+ {
+ columnKey: "amount",
+ header: T.translate("sponsor_show_purchases.amount"),
+ sortable: true
+ },
+ {
+ columnKey: "details",
+ header: "",
+ width: 100,
+ align: "center",
+ render: (row) => (
+
+ )
+ },
+ {
+ columnKey: "menu",
+ header: "",
+ width: 100,
+ align: "center",
+ render: (row) => (
+ handleMenu(row)}
+ >
+
+
+ )
+ }
+ ];
+
+ return (
+
+
+
+
+
{T.translate("sponsor_show_purchases.purchases")}
+
+
+
+ {totalCount}{" "}
+ {T.translate("sponsor_show_purchases.purchases").toLowerCase()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const mapStateToProps = ({ showPurchaseListState }) => ({
+ ...showPurchaseListState
+});
+
+export default connect(mapStateToProps, {
+ getAllSponsorPurchases,
+ exportAllSponsorPurchases,
+ approveSponsorPurchase,
+ rejectSponsorPurchase
+})(ShowPurchaseListPage);
diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js
index 2c89b5956..564ba84ec 100644
--- a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js
+++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js
@@ -247,7 +247,10 @@ describe("SponsorPurchasesTab", () => {
);
});
- expect(approveSponsorPurchase).toHaveBeenCalledWith(purchase.payment_id);
+ expect(approveSponsorPurchase).toHaveBeenCalledWith(
+ 123,
+ purchase.payment_id
+ );
expect(rejectSponsorPurchase).not.toHaveBeenCalled();
});
@@ -270,7 +273,10 @@ describe("SponsorPurchasesTab", () => {
);
});
- expect(rejectSponsorPurchase).toHaveBeenCalledWith(purchase.payment_id);
+ expect(rejectSponsorPurchase).toHaveBeenCalledWith(
+ 123,
+ purchase.payment_id
+ );
expect(approveSponsorPurchase).not.toHaveBeenCalled();
});
@@ -326,7 +332,10 @@ describe("SponsorPurchasesTab", () => {
);
});
- expect(approveSponsorPurchase).toHaveBeenCalledWith(purchase.payment_id);
+ expect(approveSponsorPurchase).toHaveBeenCalledWith(
+ 123,
+ purchase.payment_id
+ );
// Dropdown is still present — component did not unmount on error
expect(withinTableBody().getByRole("combobox")).toBeInTheDocument();
});
@@ -356,7 +365,10 @@ describe("SponsorPurchasesTab", () => {
);
});
- expect(rejectSponsorPurchase).toHaveBeenCalledWith(purchase.payment_id);
+ expect(rejectSponsorPurchase).toHaveBeenCalledWith(
+ 123,
+ purchase.payment_id
+ );
expect(withinTableBody().getByRole("combobox")).toBeInTheDocument();
});
});
diff --git a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
index 9e7de9dfa..c7d0770a2 100644
--- a/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
+++ b/src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
@@ -85,9 +85,10 @@ const SponsorPurchasesTab = ({
};
const handleStatusChange = (purchaseId, newStatus) => {
- if (newStatus === PURCHASE_STATUS.PAID) approveSponsorPurchase(purchaseId);
+ if (newStatus === PURCHASE_STATUS.PAID)
+ approveSponsorPurchase(sponsor.id, purchaseId);
if (newStatus === PURCHASE_STATUS.CANCELLED)
- rejectSponsorPurchase(purchaseId);
+ rejectSponsorPurchase(sponsor.id, purchaseId);
};
const tableColumns = [
@@ -149,7 +150,7 @@ const SponsorPurchasesTab = ({
render: (row) => (
diff --git a/src/reducers/sponsors/show-purchase-list-reducer.js b/src/reducers/sponsors/show-purchase-list-reducer.js
new file mode 100644
index 000000000..ab5cd09c2
--- /dev/null
+++ b/src/reducers/sponsors/show-purchase-list-reducer.js
@@ -0,0 +1,98 @@
+/**
+ * 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 moment from "moment-timezone";
+import { amountFromCents } from "openstack-uicore-foundation/lib/utils/money";
+import { LOGOUT_USER } from "openstack-uicore-foundation/lib/security/actions";
+import {
+ RECEIVE_ALL_SPONSOR_PURCHASES,
+ REQUEST_ALL_SPONSOR_PURCHASES,
+ SPONSOR_PURCHASE_STATUS_UPDATED
+} from "../../actions/sponsor-purchases-actions";
+import { SET_CURRENT_SUMMIT } from "../../actions/summit-actions";
+import { MILLISECONDS_TO_SECONDS } from "../../utils/constants";
+
+const DEFAULT_STATE = {
+ purchases: [],
+ order: "created",
+ orderDir: -1,
+ currentPage: 1,
+ lastPage: 1,
+ perPage: 10,
+ totalCount: 0,
+ term: ""
+};
+
+const showPurchaseListReducer = (state = DEFAULT_STATE, action) => {
+ const { type, payload } = action;
+
+ switch (type) {
+ case SET_CURRENT_SUMMIT:
+ case LOGOUT_USER: {
+ return DEFAULT_STATE;
+ }
+ case REQUEST_ALL_SPONSOR_PURCHASES: {
+ const { order, orderDir, page, perPage, term } = payload;
+
+ return {
+ ...state,
+ order,
+ orderDir,
+ forms: [],
+ currentPage: page,
+ perPage,
+ term
+ };
+ }
+ case RECEIVE_ALL_SPONSOR_PURCHASES: {
+ const {
+ current_page: currentPage,
+ total,
+ last_page: lastPage
+ } = payload.response;
+
+ const purchases = payload.response.data.map((a) => ({
+ ...a,
+ sponsor_id: a.sponsor?.id,
+ sponsor_name: a.sponsor?.company_name,
+ amount: `$${amountFromCents(a.net_amount)}`,
+ purchased: a.purchased_date
+ ? moment(a.purchased_date * MILLISECONDS_TO_SECONDS).format(
+ "YYYY/MM/DD HH:mm a"
+ )
+ : "N/A"
+ }));
+
+ return {
+ ...state,
+ purchases,
+ currentPage,
+ totalCount: total,
+ lastPage
+ };
+ }
+ case SPONSOR_PURCHASE_STATUS_UPDATED: {
+ const { paymentId, status } = payload;
+ const purchases = state.purchases.map((p) => {
+ if (p.payment_id === paymentId) return { ...p, status };
+ return p;
+ });
+
+ return { ...state, purchases };
+ }
+ default:
+ return state;
+ }
+};
+
+export default showPurchaseListReducer;
diff --git a/src/store.js b/src/store.js
index 2fbc1a166..326f84399 100644
--- a/src/store.js
+++ b/src/store.js
@@ -169,6 +169,7 @@ import sponsorCustomizedFormReducer from "./reducers/sponsors/sponsor-customized
import sponsorPageCartListReducer from "./reducers/sponsors/sponsor-page-cart-list-reducer";
import sponsorCustomizedFormItemsListReducer from "./reducers/sponsors/sponsor-customized-form-items-list-reducer.js";
import showPagesListReducer from "./reducers/sponsors/show-pages-list-reducer.js";
+import showPurchaseListReducer from "./reducers/sponsors/show-purchase-list-reducer.js";
import sponsorPagePurchaseListReducer from "./reducers/sponsors/sponsor-page-purchase-list-reducer.js";
import sponsorPagePagesListReducer from "./reducers/sponsors/sponsor-page-pages-list-reducer.js";
import sponsorPageMUListReducer from "./reducers/sponsors/sponsor-page-mu-list-reducer.js";
@@ -258,6 +259,7 @@ const reducers = persistCombineReducers(config, {
sponsorFormsListState: sponsorFormsListReducer,
sponsorFormItemsListState: sponsorFormItemsListReducer,
showPagesListState: showPagesListReducer,
+ showPurchaseListState: showPurchaseListReducer,
sponsorUsersListState: sponsorUsersListReducer,
sponsorPageFormsListState: sponsorPageFormsListReducer,
sponsorPageCartListState: sponsorPageCartListReducer,
diff --git a/src/utils/constants.js b/src/utils/constants.js
index 7b21f1953..4b217d060 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -287,6 +287,11 @@ export const PURCHASE_METHODS = {
INVOICE: "Invoice"
};
+export const PURCHASE_TYPES = {
+ ONLINE: "Online",
+ OFFLINE: "Offline"
+};
+
export const SPONSOR_USER_ASSIGNMENT_TYPE = {
EXISTING: "existing",
NEW: "new"
diff --git a/yarn.lock b/yarn.lock
index baefb5da5..9d2de6210 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9056,10 +9056,10 @@ open@^10.0.3:
is-inside-container "^1.0.0"
wsl-utils "^0.1.0"
-openstack-uicore-foundation@5.0.22:
- version "5.0.22"
- resolved "https://registry.npmjs.org/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.22.tgz#5e30b2d71fe854b08edc08a40723e8bc01f07423"
- integrity sha512-vM+K8soybtHALLCIBPccwA3tFef9xxXGQqSCKOIahwgazVzgMHlt+2hM4cDUvenGkkHf/WNogDfNCumEryJ0/Q==
+openstack-uicore-foundation@5.0.29-beta.0:
+ version "5.0.29-beta.0"
+ resolved "https://registry.yarnpkg.com/openstack-uicore-foundation/-/openstack-uicore-foundation-5.0.29-beta.0.tgz#828b8d0d6b1b6b8d4214322e32e47c1c5b3d0c68"
+ integrity sha512-N9ECX48OHfBfayeQkbbntWjGoVFozYXYGBE5XPGE6zxTgQXMH2RMZtLpQqrP/p2yHGnWBsYJlMchxecDDirIpw==
optionator@^0.9.1:
version "0.9.4"