diff --git a/package.json b/package.json
index a322371f..7ca01c10 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "openstack-uicore-foundation",
- "version": "5.0.15",
+ "version": "5.0.17-beta.4",
"description": "ui reactjs components for openstack marketing site",
"main": "lib/openstack-uicore-foundation.js",
"scripts": {
diff --git a/src/components/index.js b/src/components/index.js
index c6fc905c..38180cd3 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -80,7 +80,7 @@ export {useSnackbarMessage} from './mui/SnackbarNotification/Context'
export {default as MuiInfiniteTable} from './mui/infinite-table'
export {default as MuiEditableTable} from './mui/editable-table/mui-table-editable'
export {default as MuiTable} from './mui/table/mui-table'
-export {TotalRow as MuiTotalRow, NotesRow as MuiNotesRow} from './mui/table/extra-rows'
+export {TotalRow as MuiTotalRow, NotesRow as MuiNotesRow, FeeRow as MuiFeeRow, PaymentRow as MuiPaymentRow, RefundRow as MuiRefundRow} from './mui/table/extra-rows'
export {default as MuiFormikAsyncSelect} from './mui/formik-inputs/mui-formik-async-select'
export {default as MuiFormikCheckboxGroup} from './mui/formik-inputs/mui-formik-checkbox-group'
export {default as MuiFormikCheckbox} from './mui/formik-inputs/mui-formik-checkbox'
@@ -109,7 +109,9 @@ export {default as MuiAlertModal} from './mui/AlertModal'
export {default as MuiAuthButton} from './mui/AuthButton'
export {default as MuiCartButton} from './mui/CartButton'
export {default as MuiConfirmDeleteDialog} from './mui/ConfirmDeleteDialog'
-export {default as MuiDashboardCard} from './mui/DashboardCard'
+export {default as MuiInlineCard} from './mui/cards/InlineCard'
+export {default as MuiListCard} from './mui/cards/ListCard'
+export {default as MuiTableCard} from './mui/cards/TableCard'
export {default as MuiDownloadBtn} from './mui/DownloadBtn'
export {default as MuiLoadingOverlay} from './mui/LoadingOverlay'
export {default as MuiNavBar} from './mui/NavBar'
diff --git a/src/components/mui/DashboardCard/index.js b/src/components/mui/DashboardCard/index.js
deleted file mode 100644
index c551a149..00000000
--- a/src/components/mui/DashboardCard/index.js
+++ /dev/null
@@ -1,104 +0,0 @@
-/**
- * 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 from "react";
-import {
- Card,
- CardContent,
- Divider,
- List,
- ListItem,
- Typography
-} from "@mui/material";
-
-const Heading = ({ children }) => (
-
- {children}
-
-);
-
-const Value = ({ children }) => (
-
- {children}
-
-);
-
-const DashboardCard = ({ title, rows, columns }) => {
- const renderList = () =>
- rows.map((row, i) => (
- // eslint-disable-next-line react/no-array-index-key
-
-
- {row.label}
- {row.value}
-
- {i < rows.length - 1 && }
-
- ));
-
- const renderTable = () => {
- const header = (
-
-
- {columns.map((col, i) => (
- // eslint-disable-next-line react/no-array-index-key
- {col.label}
- ))}
-
-
-
- );
-
- const rest = rows.map((row, i) => (
- // eslint-disable-next-line react/no-array-index-key
-
-
- {columns.map((col, j) => (
- // eslint-disable-next-line react/no-array-index-key
- {row[col.key]}
- ))}
-
- {i < rows.length - 1 && }
-
- ));
-
- return [header, ...rest];
- };
-
- return (
-
-
-
- {title}
-
-
- {!columns && renderList()}
- {columns && renderTable()}
-
-
-
- );
-};
-
-export default DashboardCard;
diff --git a/src/components/mui/__tests__/dashboard-card.test.js b/src/components/mui/__tests__/dashboard-card.test.js
deleted file mode 100644
index e58be55f..00000000
--- a/src/components/mui/__tests__/dashboard-card.test.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * 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 from "react";
-import { render, screen } from "@testing-library/react";
-import "@testing-library/jest-dom";
-import DashboardCard from "../DashboardCard/index";
-
-describe("DashboardCard", () => {
- test("renders the title", () => {
- render();
- expect(screen.getByText("My Card")).toBeInTheDocument();
- });
-
- test("renders rows in list mode", () => {
- const rows = [
- { label: "Name", value: "Alice" },
- { label: "Role", value: "Admin" }
- ];
- render();
- expect(screen.getByText("Name")).toBeInTheDocument();
- expect(screen.getByText("Alice")).toBeInTheDocument();
- expect(screen.getByText("Role")).toBeInTheDocument();
- expect(screen.getByText("Admin")).toBeInTheDocument();
- });
-
- test("renders table mode with columns", () => {
- const columns = [
- { key: "name", label: "Name" },
- { key: "score", label: "Score" }
- ];
- const rows = [{ name: "Bob", score: "100" }];
- render();
- expect(screen.getByText("Name")).toBeInTheDocument();
- expect(screen.getByText("Score")).toBeInTheDocument();
- expect(screen.getByText("Bob")).toBeInTheDocument();
- expect(screen.getByText("100")).toBeInTheDocument();
- });
-
- test("renders multiple rows in table mode", () => {
- const columns = [{ key: "item", label: "Item" }];
- const rows = [{ item: "First" }, { item: "Second" }];
- render();
- expect(screen.getByText("First")).toBeInTheDocument();
- expect(screen.getByText("Second")).toBeInTheDocument();
- });
-});
diff --git a/src/components/mui/__tests__/fee-row.test.js b/src/components/mui/__tests__/fee-row.test.js
new file mode 100644
index 00000000..2ee57cc7
--- /dev/null
+++ b/src/components/mui/__tests__/fee-row.test.js
@@ -0,0 +1,62 @@
+/**
+ * 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.
+ * */
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ __esModule: true,
+ default: { translate: (key) => key }
+}));
+
+jest.mock("../../../utils/money", () => ({
+ currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}`
+}));
+
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import FeeRow from "../table/extra-rows/FeeRow";
+
+const renderInTable = (props) =>
+ render(
+
+ );
+
+describe("FeeRow", () => {
+ test("renders nothing when fee is not provided", () => {
+ const { container } = renderInTable({});
+ expect(container.querySelector("tr")).not.toBeInTheDocument();
+ });
+
+ test("renders the fee title", () => {
+ renderInTable({ fee: { title: "Processing Fee", amount: 150 } });
+ expect(screen.getByText("Processing Fee")).toBeInTheDocument();
+ });
+
+ test("renders the formatted fee amount", () => {
+ renderInTable({ fee: { title: "Service Fee", amount: 500 } });
+ expect(screen.getByText("$5.00")).toBeInTheDocument();
+ });
+
+ test("renders the i18n label key", () => {
+ renderInTable({ fee: { title: "Fee", amount: 0 } });
+ expect(screen.getByText("mui_table.payfee")).toBeInTheDocument();
+ });
+
+ test("renders a single table row", () => {
+ const { container } = renderInTable({ fee: { title: "Fee", amount: 100 } });
+ expect(container.querySelectorAll("tr")).toHaveLength(1);
+ });
+});
diff --git a/src/components/mui/__tests__/payment-row.test.js b/src/components/mui/__tests__/payment-row.test.js
new file mode 100644
index 00000000..754d7f99
--- /dev/null
+++ b/src/components/mui/__tests__/payment-row.test.js
@@ -0,0 +1,68 @@
+/**
+ * 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.
+ * */
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ __esModule: true,
+ default: { translate: (key) => key }
+}));
+
+jest.mock("../../../utils/money", () => ({
+ currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}`
+}));
+
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import PaymentRow from "../table/extra-rows/PaymentRow";
+
+// 2026-01-15 00:00:00 UTC in seconds
+const PAYMENT_TIMESTAMP = 1736899200;
+
+const renderInTable = (props) =>
+ render(
+
+ );
+
+describe("PaymentRow", () => {
+ test("renders nothing when payment is not provided", () => {
+ const { container } = renderInTable({});
+ expect(container.querySelector("tr")).not.toBeInTheDocument();
+ });
+
+ test("renders the formatted payment amount", () => {
+ renderInTable({ payment: { method: "Visa", amount: 2000, created: PAYMENT_TIMESTAMP } });
+ expect(screen.getByText("-$20.00")).toBeInTheDocument();
+ });
+
+ test("renders the payment method", () => {
+ renderInTable({ payment: { method: "Visa", amount: 1000, created: PAYMENT_TIMESTAMP } });
+ expect(screen.getByText(/Visa/)).toBeInTheDocument();
+ });
+
+ test("renders the i18n label keys", () => {
+ renderInTable({ payment: { method: "Card", amount: 500, created: PAYMENT_TIMESTAMP } });
+ expect(screen.getByText("mui_table.pay")).toBeInTheDocument();
+ expect(screen.getByText("mui_table.payment")).toBeInTheDocument();
+ });
+
+ test("renders a single table row", () => {
+ const { container } = renderInTable({
+ payment: { method: "Card", amount: 100, created: PAYMENT_TIMESTAMP }
+ });
+ expect(container.querySelectorAll("tr")).toHaveLength(1);
+ });
+});
diff --git a/src/components/mui/__tests__/refund-row.test.js b/src/components/mui/__tests__/refund-row.test.js
new file mode 100644
index 00000000..594e852e
--- /dev/null
+++ b/src/components/mui/__tests__/refund-row.test.js
@@ -0,0 +1,70 @@
+/**
+ * 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.
+ * */
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ __esModule: true,
+ default: { translate: (key) => key }
+}));
+
+jest.mock("../../../utils/money", () => ({
+ currencyAmountFromCents: (amount) => `$${(amount / 100).toFixed(2)}`
+}));
+
+import React from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import RefundRow from "../table/extra-rows/RefundRow";
+
+const renderInTable = (props) =>
+ render(
+
+ );
+
+describe("RefundRow", () => {
+ test("renders nothing when refund is not provided", () => {
+ const { container } = renderInTable({});
+ expect(container.querySelector("tr")).not.toBeInTheDocument();
+ });
+
+ test("renders the formatted refund amount", () => {
+ renderInTable({ refund: { reason: "Duplicate", status: "completed", amount: 3000 } });
+ expect(screen.getByText("-$30.00")).toBeInTheDocument();
+ });
+
+ test("renders the refund reason", () => {
+ renderInTable({ refund: { reason: "Customer request", status: "pending", amount: 1000 } });
+ expect(screen.getByText("Customer request")).toBeInTheDocument();
+ });
+
+ test("renders the refund status", () => {
+ renderInTable({ refund: { reason: "Error", status: "approved", amount: 500 } });
+ expect(screen.getByText("approved")).toBeInTheDocument();
+ });
+
+ test("renders the i18n label keys", () => {
+ renderInTable({ refund: { reason: "Test", status: "pending", amount: 100 } });
+ expect(screen.getByText("mui_table.ref")).toBeInTheDocument();
+ expect(screen.getByText("mui_table.refund")).toBeInTheDocument();
+ });
+
+ test("renders a single table row", () => {
+ const { container } = renderInTable({
+ refund: { reason: "Test", status: "pending", amount: 100 }
+ });
+ expect(container.querySelectorAll("tr")).toHaveLength(1);
+ });
+});
diff --git a/src/components/mui/__tests__/table-card.test.js b/src/components/mui/__tests__/table-card.test.js
new file mode 100644
index 00000000..b16b6123
--- /dev/null
+++ b/src/components/mui/__tests__/table-card.test.js
@@ -0,0 +1,58 @@
+/**
+ * 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 from "react";
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import TableCard from "../cards/TableCard";
+
+const columns = [
+ { key: "name", label: "Name" },
+ { key: "qty", label: "Qty" }
+];
+
+const rows = [
+ { name: "Alpha", qty: 1 },
+ { name: "Beta", qty: 2 }
+];
+
+describe("TableCard", () => {
+ test("renders the title", () => {
+ render();
+ expect(screen.getByText("My Table")).toBeInTheDocument();
+ });
+
+ test("renders column headers", () => {
+ render();
+ expect(screen.getByText("Name")).toBeInTheDocument();
+ expect(screen.getByText("Qty")).toBeInTheDocument();
+ });
+
+ test("renders row values", () => {
+ render();
+ expect(screen.getByText("Alpha")).toBeInTheDocument();
+ expect(screen.getByText("Beta")).toBeInTheDocument();
+ expect(screen.getByText("1")).toBeInTheDocument();
+ expect(screen.getByText("2")).toBeInTheDocument();
+ });
+
+ test("renders empty rows without crashing", () => {
+ render();
+ expect(screen.getByText("Empty")).toBeInTheDocument();
+ });
+
+ test("renders empty columns without crashing", () => {
+ render();
+ expect(screen.getByText("NoColumns")).toBeInTheDocument();
+ });
+});
diff --git a/src/components/mui/cards/InlineCard/index.js b/src/components/mui/cards/InlineCard/index.js
new file mode 100644
index 00000000..1d41067e
--- /dev/null
+++ b/src/components/mui/cards/InlineCard/index.js
@@ -0,0 +1,53 @@
+/**
+ * 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 from "react";
+import {
+ Card,
+ CardContent,
+ Divider,
+ List,
+ ListItem,
+ Typography
+} from "@mui/material";
+import Heading from "../components/Heading";
+import Value from "../components/Value";
+
+const InlineCard = ({ title, rows }) => (
+
+
+
+ {title}
+
+
+ {(rows ?? []).map((row, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+
+ {row.label}
+ {row.value}
+
+ {i < rows.length - 1 && }
+
+ ))}
+
+
+
+);
+
+export default InlineCard;
diff --git a/src/components/mui/cards/ListCard/index.js b/src/components/mui/cards/ListCard/index.js
new file mode 100644
index 00000000..6d0372ba
--- /dev/null
+++ b/src/components/mui/cards/ListCard/index.js
@@ -0,0 +1,44 @@
+/**
+ * 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 from "react";
+import { Card, CardContent, List, ListItem, Typography } from "@mui/material";
+import Heading from "../components/Heading";
+import Value from "../components/Value";
+
+const ListCard = ({ title, rows }) => (
+
+
+
+ {title}
+
+
+ {(rows ?? []).map((row, i) => (
+
+ {row.label}
+ {row.value}
+
+ ))}
+
+
+
+);
+
+export default ListCard;
diff --git a/src/components/mui/cards/TableCard/index.js b/src/components/mui/cards/TableCard/index.js
new file mode 100644
index 00000000..1e373437
--- /dev/null
+++ b/src/components/mui/cards/TableCard/index.js
@@ -0,0 +1,62 @@
+/**
+ * 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 from "react";
+import {
+ Card,
+ CardContent,
+ Divider,
+ List,
+ ListItem,
+ Typography
+} from "@mui/material";
+import Value from "../components/Value";
+import Heading from "../components/Heading";
+
+const TableCard = ({ title, rows = [], columns = [] }) => (
+
+
+
+ {title}
+
+
+
+ {columns.map((col, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+ {col.label}
+ ))}
+
+
+ {rows.map((row, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+
+ {columns.map((col, j) => (
+ // eslint-disable-next-line react/no-array-index-key
+ {row[col.key]}
+ ))}
+
+ {i < rows.length - 1 && (
+
+ )}
+
+ ))}
+
+
+
+);
+
+export default TableCard;
diff --git a/src/components/mui/cards/components/Heading.js b/src/components/mui/cards/components/Heading.js
new file mode 100644
index 00000000..9b0eb1fe
--- /dev/null
+++ b/src/components/mui/cards/components/Heading.js
@@ -0,0 +1,14 @@
+import { Typography } from "@mui/material";
+import React from "react";
+
+const Heading = ({ children }) => (
+
+ {children}
+
+);
+
+export default Heading;
diff --git a/src/components/mui/cards/components/Value.js b/src/components/mui/cards/components/Value.js
new file mode 100644
index 00000000..d46cadfd
--- /dev/null
+++ b/src/components/mui/cards/components/Value.js
@@ -0,0 +1,14 @@
+import { Typography } from "@mui/material";
+import React from "react";
+
+const Value = ({ children }) => (
+
+ {children}
+
+);
+
+export default Value;
diff --git a/src/components/mui/cards/index.js b/src/components/mui/cards/index.js
new file mode 100644
index 00000000..dd985b56
--- /dev/null
+++ b/src/components/mui/cards/index.js
@@ -0,0 +1,3 @@
+export { default as InlineCard } from "./InlineCard";
+export { default as ListCard } from "./ListCard";
+export { default as TableCard } from "./TableCard";
diff --git a/src/components/mui/table/extra-rows/FeeRow.jsx b/src/components/mui/table/extra-rows/FeeRow.jsx
new file mode 100644
index 00000000..e4914acd
--- /dev/null
+++ b/src/components/mui/table/extra-rows/FeeRow.jsx
@@ -0,0 +1,52 @@
+/**
+ * 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 from "react";
+import T from "i18n-react/dist/i18n-react";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
+import Typography from "@mui/material/Typography";
+import { currencyAmountFromCents } from "../../../../utils/money";
+
+const FeeRow = ({ fee, colGap = 3, trailing = 0 }) => {
+ if (!fee) return null;
+
+ return (
+
+ {T.translate("mui_table.payfee")}
+
+
+ {fee.title}
+
+
+ {[...Array(colGap)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+
+ {currencyAmountFromCents(fee.amount)}
+
+
+ {[...Array(trailing)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+ );
+};
+
+export default FeeRow;
diff --git a/src/components/mui/table/extra-rows/PaymentRow.jsx b/src/components/mui/table/extra-rows/PaymentRow.jsx
new file mode 100644
index 00000000..dc61d88f
--- /dev/null
+++ b/src/components/mui/table/extra-rows/PaymentRow.jsx
@@ -0,0 +1,68 @@
+/**
+ * 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 from "react";
+import T from "i18n-react/dist/i18n-react";
+import moment from "moment-timezone";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
+import Typography from "@mui/material/Typography";
+import { currencyAmountFromCents } from "../../../../utils/money";
+import { DATETIME_FORMAT, MILLISECONDS_IN_SECOND } from "../../../../utils/constants";
+
+const PaymentRow = ({ payment, colGap = 1, trailing = 0 }) => {
+
+ if (!payment) return null;
+
+ return (
+
+ {T.translate("mui_table.pay")}
+
+
+ {T.translate("mui_table.payment")}
+
+
+
+
+ {T.translate("mui_table.paid_via")} {payment.method}
+
+
+
+
+ {moment(payment.created * MILLISECONDS_IN_SECOND).format(DATETIME_FORMAT)}
+
+
+ {[...Array(colGap)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+
+ -{currencyAmountFromCents(payment.amount)}
+
+
+ {[...Array(trailing)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+ );
+};
+
+export default PaymentRow;
diff --git a/src/components/mui/table/extra-rows/RefundRow.jsx b/src/components/mui/table/extra-rows/RefundRow.jsx
new file mode 100644
index 00000000..9b3e9e27
--- /dev/null
+++ b/src/components/mui/table/extra-rows/RefundRow.jsx
@@ -0,0 +1,66 @@
+/**
+ * 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 from "react";
+import T from "i18n-react/dist/i18n-react";
+import TableRow from "@mui/material/TableRow";
+import TableCell from "@mui/material/TableCell";
+import Typography from "@mui/material/Typography";
+import { currencyAmountFromCents } from "../../../../utils/money";
+
+const RefundRow = ({ refund, colGap = 1, trailing = 0 }) => {
+
+ if (!refund) return null;
+
+ return (
+
+ {T.translate("mui_table.ref")}
+
+
+ {T.translate("mui_table.refund")}
+
+
+
+
+ {refund.reason}
+
+
+
+
+ {refund.status}
+
+
+ {[...Array(colGap)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+
+ -{currencyAmountFromCents(refund.amount)}
+
+
+ {[...Array(trailing)].map((_, i) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ ))}
+
+ );
+};
+
+export default RefundRow;
diff --git a/src/components/mui/table/extra-rows/TotalRow.jsx b/src/components/mui/table/extra-rows/TotalRow.jsx
index e72701cd..41bceb6c 100644
--- a/src/components/mui/table/extra-rows/TotalRow.jsx
+++ b/src/components/mui/table/extra-rows/TotalRow.jsx
@@ -11,19 +11,22 @@
* limitations under the License.
* */
+import React from "react";
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 }) => {
+const TotalRow = ({ columns, targetCol, total, trailing = 0, label = null, rowSx = {} }) => {
+
+ const totalLabel = label || T.translate("mui_table.total");
+
return (
-
+
{columns.map((col, i) => {
if (i === 0)
return (
- {T.translate("mui_table.total")}
+ {totalLabel}
);
if (col.columnKey === targetCol)
diff --git a/src/components/mui/table/extra-rows/index.js b/src/components/mui/table/extra-rows/index.js
index 6fdc4792..18f36ef1 100644
--- a/src/components/mui/table/extra-rows/index.js
+++ b/src/components/mui/table/extra-rows/index.js
@@ -13,3 +13,6 @@
export { default as TotalRow } from "./TotalRow";
export { default as NotesRow } from "./NotesRow";
+export { default as FeeRow } from "./FeeRow";
+export { default as PaymentRow } from "./PaymentRow";
+export { default as RefundRow } from "./RefundRow";
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 17cb1db8..7f107230 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -58,7 +58,14 @@
"rows_per_page": "Rows per page",
"sorted_desc": "sorted descending",
"sorted_asc": "sorted ascending",
- "total": "Total"
+ "total": "Total",
+ "pay": "PAY",
+ "payment": "Payment",
+ "paid_via": "Paid via",
+ "ref": "REF",
+ "refund": "Refund",
+ "payfee": "PAYFEE",
+ "amount_due": "AMOUNT DUE"
},
"meta_fields": {
"delete_value_warning": "Please verify you want to delete the added value",
diff --git a/src/utils/constants.js b/src/utils/constants.js
index a70f1a95..e7dbe3eb 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -76,4 +76,7 @@ export const FILE_UPLOAD_STATUS_BKGR_COLOR = {
[FILE_UPLOAD_STATUS.DEADLINE_MISSED]: "#D32F2F0A",
[FILE_UPLOAD_STATUS.DEADLINE_ALERT]: "#EF6C000A",
[FILE_UPLOAD_STATUS.PENDING]: "white"
-};
\ No newline at end of file
+};
+
+export const DATE_FORMAT = "MM/DD/YYYY";
+export const DATETIME_FORMAT = "MM/DD/YYYY hh:mm a";
\ No newline at end of file
diff --git a/webpack.common.js b/webpack.common.js
index 6c198ca9..e4966a97 100644
--- a/webpack.common.js
+++ b/webpack.common.js
@@ -132,7 +132,10 @@ module.exports = {
'components/mui/auth-button': './src/components/mui/AuthButton/index.js',
'components/mui/cart-button': './src/components/mui/CartButton/index.js',
'components/mui/confirm-delete-dialog': './src/components/mui/ConfirmDeleteDialog/index.js',
- 'components/mui/dashboard-card': './src/components/mui/DashboardCard/index.js',
+ 'components/mui/cards': './src/components/mui/cards/index.js',
+ 'components/mui/cards/inline-card': './src/components/mui/cards/InlineCard/index.js',
+ 'components/mui/cards/list-card': './src/components/mui/cards/ListCard/index.js',
+ 'components/mui/cards/table-card': './src/components/mui/cards/TableCard/index.js',
'components/mui/download-btn': './src/components/mui/DownloadBtn/index.js',
'components/mui/loading-overlay': './src/components/mui/LoadingOverlay/index.jsx',
'components/mui/nav-bar': './src/components/mui/NavBar/index.js',