diff --git a/package.json b/package.json index 6f9e36d..0e4c4f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openstack-uicore-foundation", - "version": "5.0.17", + "version": "5.0.20-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 6e956bb..cc3dff0 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -76,6 +76,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 {default as MuiSponsorOrderGrid} from './mui/SponsorOrderGrid' export {TotalRow as MuiTotalRow, NotesRow as MuiNotesRow, FeeRow as MuiFeeRow, PaymentRow as MuiPaymentRow, RefundRow as MuiRefundRow, DiscountRow as MuiDiscountRow} 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' @@ -115,6 +116,7 @@ export {default as MuiOrderSummary} from './mui/OrderSummary' export {default as MuiStatusChip} from './mui/StatusChip' export {default as MuiUploadBtn} from './mui/UploadBtn' export {default as MuiUploadDialog} from './mui/UploadDialog' +export {default as MuiInfoNote} from './mui/InfoNote' // these include 3rd party deps // export {default as ExtraQuestionsForm } from './extra-questions/index.js'; diff --git a/src/components/mui/FormItemTable/index.js b/src/components/mui/FormItemTable/index.js index 98f77f5..468789c 100644 --- a/src/components/mui/FormItemTable/index.js +++ b/src/components/mui/FormItemTable/index.js @@ -91,7 +91,9 @@ const FormItemTable = ({ (mf) => mf.class_field === "Item" && mf.is_required ); const hasMissingFields = requiredFields.some((mf) => { - const value = values[`i-${row.form_item_id}-c-item-f-${mf.type_id}`]; + const value = values[`i-${row.form_item_id}-c-Item-f-${mf.type_id}`]; + if (mf.type === "CheckBoxList") return !Array.isArray(value) || value.length === 0; + if (mf.type === "CheckBox") return value !== true; return value === undefined || value === null || value === ""; }); diff --git a/src/components/mui/InfoNote/index.jsx b/src/components/mui/InfoNote/index.jsx new file mode 100644 index 0000000..413735f --- /dev/null +++ b/src/components/mui/InfoNote/index.jsx @@ -0,0 +1,17 @@ +import React from "react"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; + +const InfoNote = ({ message, sx }) => ( + + + + {message} + + +); + +export default InfoNote; diff --git a/src/components/mui/SponsorOrderGrid/__tests__/SponsorOrderGrid.test.js b/src/components/mui/SponsorOrderGrid/__tests__/SponsorOrderGrid.test.js new file mode 100644 index 0000000..763dde3 --- /dev/null +++ b/src/components/mui/SponsorOrderGrid/__tests__/SponsorOrderGrid.test.js @@ -0,0 +1,171 @@ +/** + * 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)}` +})); + +jest.mock("../../../../utils/constants", () => ({ + SPONSOR_FORMS_METAFIELD_CLASS: { FORM: "Form", ITEM: "Item" } +})); + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import SponsorOrderGrid from "../index"; + +const makeItem = (overrides = {}) => ({ + line_id: 1, + quantity: 1, + amount: 10000, + current_rate: 5000, + canceled_by_id: null, + type: { name: "Booth" }, + meta_fields: [], + ...overrides +}); + +const makeForm = (overrides = {}) => ({ + id: 10, + code: "GOLD", + name: "Gold Sponsor", + addon_name: "Premium", + discount: null, + discount_total: null, + items: [makeItem()], + ...overrides +}); + +const defaultProps = { + lines: [makeForm()], + total: 10000 +}; + +describe("SponsorOrderGrid", () => { + test("renders column headers", () => { + render(); + expect(screen.getByText("sponsor_order_grid.code")).toBeInTheDocument(); + expect(screen.getByText("sponsor_order_grid.contents")).toBeInTheDocument(); + expect(screen.getByText("sponsor_order_grid.addon")).toBeInTheDocument(); + expect(screen.getByText("sponsor_order_grid.details")).toBeInTheDocument(); + expect(screen.getByText("sponsor_order_grid.rate")).toBeInTheDocument(); + expect(screen.getByText("sponsor_order_grid.amount")).toBeInTheDocument(); + }); + + test("renders item code and name", () => { + render(); + expect(screen.getByText("GOLD")).toBeInTheDocument(); + expect(screen.getByText("Gold Sponsor")).toBeInTheDocument(); + }); + + test("renders formatted amount and rate", () => { + render(); + expect(screen.getByText("$100.00")).toBeInTheDocument(); + expect(screen.getByText("$50.00")).toBeInTheDocument(); + }); + + test("renders no-items message when lines is empty", () => { + render(); + expect(screen.getByText("mui_table.no_items")).toBeInTheDocument(); + }); + + test("renders no-items message when lines is undefined", () => { + render(); + expect(screen.getByText("mui_table.no_items")).toBeInTheDocument(); + }); + + test("filters out items with zero quantity", () => { + const lines = [makeForm({ items: [makeItem({ quantity: 0 })] })]; + render(); + expect(screen.queryByText("$100.00")).not.toBeInTheDocument(); + }); + + test("does not render action column when callbacks are absent", () => { + render(); + expect( + screen.queryByText("sponsor_order_grid.action") + ).not.toBeInTheDocument(); + }); + + test("renders action column header when both callbacks are provided", () => { + render( + + ); + expect(screen.getByText("sponsor_order_grid.action")).toBeInTheDocument(); + }); + + test("renders delete button for active item and calls onCancelForm on click", () => { + const onCancelForm = jest.fn(); + render( + + ); + const deleteButton = screen.getByTestId + ? document.querySelector('[data-testid="DeleteIcon"]') + : null; + const button = document.querySelector("button[aria-label]") || document.querySelector("tbody button"); + fireEvent.click(button); + expect(onCancelForm).toHaveBeenCalledTimes(1); + }); + + test("renders undo button for cancelled item and calls onUndoCancelForm on click", () => { + const onUndoCancelForm = jest.fn(); + const lines = [makeForm({ items: [makeItem({ canceled_by_id: 99 })] })]; + render( + + ); + const button = document.querySelector("tbody button"); + fireEvent.click(button); + expect(onUndoCancelForm).toHaveBeenCalledTimes(1); + }); + + test("uses amountDue label when amountDue prop is provided", () => { + render(); + expect( + screen.getByText("sponsor_order_grid.amount_due") + ).toBeInTheDocument(); + }); + + test("renders meta_field values in item details", () => { + const item = makeItem({ + meta_fields: [ + { + id: 1, + name: "Booth Size", + class_field: "Form", + current_value: "Large", + values: [] + } + ] + }); + render(); + expect(screen.getByText(/Booth Size/)).toBeInTheDocument(); + }); +}); diff --git a/src/components/mui/SponsorOrderGrid/index.js b/src/components/mui/SponsorOrderGrid/index.js new file mode 100644 index 0000000..adf695a --- /dev/null +++ b/src/components/mui/SponsorOrderGrid/index.js @@ -0,0 +1,270 @@ +/** + * 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 {DiscountRow, FeeRow, NotesRow, PaymentRow, RefundRow, TotalRow} from "../table/extra-rows"; +import IconButton from "@mui/material/IconButton"; +import DeleteIcon from "@mui/icons-material/Delete"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import Box from "@mui/material/Box"; +import Paper from "@mui/material/Paper"; +import TableContainer from "@mui/material/TableContainer"; +import TableRow from "@mui/material/TableRow"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import Table from "@mui/material/Table"; +import TableHead from "@mui/material/TableHead"; +import {currencyAmountFromCents} from "../../../utils/money"; +import {SPONSOR_FORMS_METAFIELD_CLASS} from "../../../utils/constants"; + +const mapOrderData = (forms, showItemDescription) => { + if (!forms) return []; + + return forms.map((form) => ({ + ...form, + items: form.items + .filter((it) => it.quantity) + .map((it) => { + const formMetaFields = it.meta_fields.filter( + (mf) => mf.class_field === SPONSOR_FORMS_METAFIELD_CLASS.FORM + ); + + const itemDetails = [it.type?.name]; + + // item details + if (showItemDescription) { + itemDetails.push( + ...formMetaFields.map((mf) => { + const val = + mf.values?.length > 0 + ? mf.values.find((v) => v.id === mf.current_value)?.name + : mf.current_value; + return ( +
+ {mf.name}: {val} +
+ ); + }) + ); + + itemDetails.push(
); // spacer + itemDetails.push( +
+ {T.translate("sponsor_order_grid.total")}: {it.quantity} +
+ ); + } + + const amount = currencyAmountFromCents(it.amount || 0); + const lineId = it.line_id; + const cancelled = !!it.canceled_by_id; + const rate = currencyAmountFromCents(it.current_rate || 0); + + return { + id: lineId, + code: form.code, + name: form.name, + rate, + addon_name: form.addon_name, + item_name: itemDetails, + amount, + cancelled + }; + }) + })); +}; + +const SponsorOrderGrid = ({ + lines, + notes, + payments, + refunds, + fees, + total, + amountDue, + withDescription = false, + onCancelForm, + onUndoCancelForm + }) => { + const data = mapOrderData(lines, withDescription); + const showActionCol = onCancelForm && onUndoCancelForm; + const trailingCols = showActionCol ? 1 : 0; + + const columns = [ + { + columnKey: "code", + header: T.translate("sponsor_order_grid.code") + }, + { + columnKey: "name", + header: T.translate("sponsor_order_grid.contents") + }, + { + columnKey: "addon_name", + header: T.translate("sponsor_order_grid.addon") + }, + { + columnKey: "item_name", + header: T.translate("sponsor_order_grid.details") + }, + { + columnKey: "rate", + header: T.translate("sponsor_order_grid.rate") + }, + { + columnKey: "amount", + header: T.translate("sponsor_order_grid.amount") + } + ]; + + if (showActionCol) { + columns.push({ + columnKey: "actions", + header: T.translate("sponsor_order_grid.action"), + align: "center", + render: (row) => { + if (row.cancelled) { + return ( + onUndoCancelForm(row)}> + {" "} + {T.translate("general.undo").toUpperCase()} + + ); + } + + return ( + onCancelForm(row)}> + + + ); + } + }); + } + + return ( + + + + + {/* TABLE HEADER */} + + + {columns.map((col) => ( + + {col.header} + + ))} + + + + {data.map((form) => { + const rows = form.items.map((row) => ( + + {columns.map((col) => ( + + {col.render ? ( + col.render(row) + ) : ( + + {row[col.columnKey]} + + )} + + ))} + + )); + + rows.push( + + ); + + return rows; + })} + {fees && + fees.map((fee) => ( + + ))} + {refunds && + refunds.map((refund) => ( + + ))} + {payments && + payments.map((payment) => ( + + ))} + {notes && + notes.map((note) => ( + + ))} + + + {data.length === 0 && ( + + + {T.translate("mui_table.no_items")} + + + )} + +
+
+
+
+ ); +}; + +export default SponsorOrderGrid; diff --git a/src/i18n/en.json b/src/i18n/en.json index a6ccb50..ff5d5eb 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -121,5 +121,17 @@ "button_cta": "PAY {amount}", "title": "Select a payment method", "payment_confirmation_error": "Payment confirmation failed" + }, + "sponsor_order_grid": { + "code": "Code", + "contents": "Contents", + "addon": "Add-on", + "details": "Details", + "discount": "Discount", + "amount": "Amount", + "amount_due": "AMOUNT DUE", + "total": "Total", + "rate": "Rate", + "action": "Action" } } diff --git a/src/utils/constants.js b/src/utils/constants.js index e7dbe3e..85e190f 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -3,6 +3,7 @@ export const TWO_DECIMAL_PLACES = 2; export const THREE_DECIMAL_PLACES = 3; export const ONE_CENT = 1n; export const ZERO_INT = 0; +export const BPS = 100; export const CODE_200 = 200; @@ -79,4 +80,9 @@ export const FILE_UPLOAD_STATUS_BKGR_COLOR = { }; export const DATE_FORMAT = "MM/DD/YYYY"; -export const DATETIME_FORMAT = "MM/DD/YYYY hh:mm a"; \ No newline at end of file +export const DATETIME_FORMAT = "MM/DD/YYYY hh:mm a"; + +export const SPONSOR_FORMS_METAFIELD_CLASS = { + FORM: "Form", + ITEM: "Item" +}; \ No newline at end of file diff --git a/src/utils/money.js b/src/utils/money.js index 2197534..e4295cd 100644 --- a/src/utils/money.js +++ b/src/utils/money.js @@ -12,24 +12,26 @@ * */ import { - CENTS_FACTOR, - THREE_DECIMAL_PLACES, - TWO_DECIMAL_PLACES, - ZERO_INT, - ONE_CENT + BPS, + CENTS_FACTOR, + DISCOUNT_TYPES, + ONE_CENT, + THREE_DECIMAL_PLACES, + TWO_DECIMAL_PLACES, + ZERO_INT } from "./constants"; const CURRENCY_SYMBOL = { - USD: "$", - EUR: "€", - GBP: "£", - CAD: "C$", - AUD: "A$", - NZD: "NZ$", - CHF: "CHF", - ARS: "AR$", - BRL: "R$", - MXN: "MX$", + USD: "$", + EUR: "€", + GBP: "£", + CAD: "C$", + AUD: "A$", + NZD: "NZ$", + CHF: "CHF", + ARS: "AR$", + BRL: "R$", + MXN: "MX$", }; /** @@ -54,54 +56,54 @@ const CURRENCY_SYMBOL = { * - Throws on invalid formats (does not silently return 0). */ export function amountToCents(amount) { - if (amount == null) throw new Error("amount is required"); - - let s = String(amount).trim(); - - // Normalize common separators: - // - "1,234.56" => "1234.56" (remove thousands separators) - // - "1234,56" => "1234.56" (convert decimal comma to dot) - if (s.includes(",") && s.includes(".")) { - s = s.replace(/,/g, ""); - } else if (s.includes(",") && !s.includes(".")) { - s = s.replace(",", "."); - } - - // Validate: digits optionally followed by '.' and more digits - if (!/^\d+(\.\d+)?$/.test(s)) { - throw new Error(`Invalid money format: "${amount}"`); - } - - const [intPart, fracRaw = ""] = s.split("."); - - // Pad at least 3 fractional digits so we can: - // - take 2 digits for cents - // - take the 3rd digit to decide rounding - const fracPadded = (`${fracRaw }000`); - - const tenths = fracPadded[0] ?? "0"; // 1st decimal digit - const hundredths = fracPadded[1] ?? "0"; // 2nd decimal digit (cents) - const thousandths = fracPadded[2] ?? "0"; // 3rd decimal digit (rounding decision) - - // If there are more than 3 decimals, we track if any non-zero exists after the 3rd. - // This can matter for policies like bankers rounding; here it's mainly informational. - const trailing = - fracRaw.length > THREE_DECIMAL_PLACES - ? fracRaw.slice(THREE_DECIMAL_PLACES) - : ""; - const hasTrailingNonZero = /[1-9]/.test(trailing); - - // Build cents as integer: (dollars * 100) + (first two decimal digits) - let cents = BigInt(intPart) * CENTS_FACTOR + BigInt(tenths + hundredths); - - // Half-up rounding: - // - If the 3rd digit is >= 5, round up by 1 cent. - // - If there are more digits beyond the 3rd, "5xxx" should also round up. - const roundUp = thousandths > "5" || thousandths === "5" || (thousandths === "5" && hasTrailingNonZero); - - if (roundUp) cents += ONE_CENT; - - return Number(cents); + if (amount == null) throw new Error("amount is required"); + + let s = String(amount).trim(); + + // Normalize common separators: + // - "1,234.56" => "1234.56" (remove thousands separators) + // - "1234,56" => "1234.56" (convert decimal comma to dot) + if (s.includes(",") && s.includes(".")) { + s = s.replace(/,/g, ""); + } else if (s.includes(",") && !s.includes(".")) { + s = s.replace(",", "."); + } + + // Validate: digits optionally followed by '.' and more digits + if (!/^\d+(\.\d+)?$/.test(s)) { + throw new Error(`Invalid money format: "${amount}"`); + } + + const [intPart, fracRaw = ""] = s.split("."); + + // Pad at least 3 fractional digits so we can: + // - take 2 digits for cents + // - take the 3rd digit to decide rounding + const fracPadded = (`${fracRaw}000`); + + const tenths = fracPadded[0] ?? "0"; // 1st decimal digit + const hundredths = fracPadded[1] ?? "0"; // 2nd decimal digit (cents) + const thousandths = fracPadded[2] ?? "0"; // 3rd decimal digit (rounding decision) + + // If there are more than 3 decimals, we track if any non-zero exists after the 3rd. + // This can matter for policies like bankers rounding; here it's mainly informational. + const trailing = + fracRaw.length > THREE_DECIMAL_PLACES + ? fracRaw.slice(THREE_DECIMAL_PLACES) + : ""; + const hasTrailingNonZero = /[1-9]/.test(trailing); + + // Build cents as integer: (dollars * 100) + (first two decimal digits) + let cents = BigInt(intPart) * CENTS_FACTOR + BigInt(tenths + hundredths); + + // Half-up rounding: + // - If the 3rd digit is >= 5, round up by 1 cent. + // - If there are more digits beyond the 3rd, "5xxx" should also round up. + const roundUp = thousandths > "5" || thousandths === "5" || (thousandths === "5" && hasTrailingNonZero); + + if (roundUp) cents += ONE_CENT; + + return Number(cents); } /** @@ -119,36 +121,36 @@ export function amountToCents(amount) { * - Always returns a string with exactly 2 decimal places. */ export function amountFromCents(cents) { - let c; - - // Normalize input to BigInt safely - if (typeof cents === "bigint") { - c = cents; - } else if (typeof cents === "number") { - // Ensure it's a safe integer before converting to BigInt - if (!Number.isSafeInteger(cents)) { - throw new Error("cents must be a safe integer Number (or pass BigInt/string)."); - } - c = BigInt(cents); - } else if (typeof cents === "string") { - const s = cents.trim(); - if (!/^\d+$/.test(s)) { - throw new Error("cents string must contain digits only (e.g., '1234')."); - } - c = BigInt(s); - } else { - throw new Error("cents must be a bigint, number, or numeric string."); + let c; + + // Normalize input to BigInt safely + if (typeof cents === "bigint") { + c = cents; + } else if (typeof cents === "number") { + // Ensure it's a safe integer before converting to BigInt + if (!Number.isSafeInteger(cents)) { + throw new Error("cents must be a safe integer Number (or pass BigInt/string)."); } - - if (c < ZERO_INT) { - throw new Error("cents must be non-negative."); + c = BigInt(cents); + } else if (typeof cents === "string") { + const s = cents.trim(); + if (!/^\d+$/.test(s)) { + throw new Error("cents string must contain digits only (e.g., '1234')."); } + c = BigInt(s); + } else { + throw new Error("cents must be a bigint, number, or numeric string."); + } + + if (c < ZERO_INT) { + throw new Error("cents must be non-negative."); + } - const amount = c / CENTS_FACTOR; - const remainder = c % CENTS_FACTOR; + const amount = c / CENTS_FACTOR; + const remainder = c % CENTS_FACTOR; - // Always pad remainder to 2 digits - return `${amount.toString()}.${remainder.toString().padStart(TWO_DECIMAL_PLACES, "0")}`; + // Always pad remainder to 2 digits + return `${amount.toString()}.${remainder.toString().padStart(TWO_DECIMAL_PLACES, "0")}`; } /** @@ -156,14 +158,14 @@ export function amountFromCents(cents) { * @param currency * @returns {string} */ -export function currencyAmountFromCents(cents, currency = "USD"){ - if (typeof cents !== "number" || !Number.isInteger(cents)) { - throw new Error("cents must be an integer number"); - } - - const amount = amountFromCents(cents); // "12.34" - const symbol = CURRENCY_SYMBOL[currency] ?? "$"; - return `${symbol}${amount}`; +export function currencyAmountFromCents(cents, currency = "USD") { + if (typeof cents !== "number" || !Number.isInteger(cents)) { + throw new Error("cents must be an integer number"); + } + + const amount = amountFromCents(cents); // "12.34" + const symbol = CURRENCY_SYMBOL[currency] ?? "$"; + return `${symbol}${amount}`; } /** @@ -172,17 +174,29 @@ export function currencyAmountFromCents(cents, currency = "USD"){ * @returns {number} - The amount converted to cents (e.g., 30). */ export const parsePrice = (priceString) => { - if (priceString == null) throw new Error("priceString is required"); + if (priceString == null) throw new Error("priceString is required"); - let s = String(priceString).trim(); + let s = String(priceString).trim(); - // Reject negatives explicitly (per your requirement). - if (s.includes("-")) throw new Error("Negative amounts are not allowed"); + // Reject negatives explicitly (per your requirement). + if (s.includes("-")) throw new Error("Negative amounts are not allowed"); - // Keep only digits and separators. Remove currency symbols/letters/spaces. - s = s.replace(/[^\d.,]/g, ""); - if (!s) throw new Error(`Invalid price: "${priceString}"`); + // Keep only digits and separators. Remove currency symbols/letters/spaces. + s = s.replace(/[^\d.,]/g, ""); + if (!s) throw new Error(`Invalid price: "${priceString}"`); - // Delegate exact cents conversion (no floats) - return amountToCents(s); // <- your safe BigInt cents converter + // Delegate exact cents conversion (no floats) + return amountToCents(s); // <- your safe BigInt cents converter }; + +/** + * Formats a discount value in BPS or CENTS to string. + * @param {number} amount - The discount in BPS or CENTS(e.g., 300 = 3%). + * @param {string} type - The discount type: "Amount" or "Rate". + * @returns {string} - The discount converted to string (e.g., $5). + */ +export const formatDiscount = (amount, type) => { + if (type === DISCOUNT_TYPES.AMOUNT) return currencyAmountFromCents(amount); + if (type === DISCOUNT_TYPES.RATE) return `${amount / BPS}%`; // transform from bps to percentage + return ""; +}; \ No newline at end of file diff --git a/webpack.common.js b/webpack.common.js index e4966a9..eae3e60 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -140,10 +140,12 @@ module.exports = { 'components/mui/loading-overlay': './src/components/mui/LoadingOverlay/index.jsx', 'components/mui/nav-bar': './src/components/mui/NavBar/index.js', 'components/mui/order-summary': './src/components/mui/OrderSummary/index.jsx', + 'components/mui/sponsor-order-grid': './src/components/mui/SponsorOrderGrid/index.js', 'components/mui/status-chip': './src/components/mui/StatusChip/index.js', 'components/mui/stripe-payment': './src/components/mui/StripePayment/index.jsx', 'components/mui/upload-btn': './src/components/mui/UploadBtn/index.js', 'components/mui/upload-dialog': './src/components/mui/UploadDialog/index.js', + 'components/mui/info-note': './src/components/mui/InfoNote/index.jsx', // models 'models/index': './src/models',