diff --git a/src/components/mui/__tests__/mui-formik-file-size-field.test.js b/src/components/mui/__tests__/mui-formik-file-size-field.test.js
new file mode 100644
index 000000000..fc00b79fd
--- /dev/null
+++ b/src/components/mui/__tests__/mui-formik-file-size-field.test.js
@@ -0,0 +1,59 @@
+import React from "react";
+import { render, screen, act } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Formik, Form } from "formik";
+import "@testing-library/jest-dom";
+import MuiFormikFilesizeField from "../formik-inputs/mui-formik-file-size-field";
+import { BYTES_PER_MB } from "../../../utils/constants";
+
+const renderWithFormik = (props, initialValues = { max_file_size: 0 }) =>
+ render(
+
+
+
+ );
+
+describe("MuiFormikFilesizeField", () => {
+ describe("display and store", () => {
+ it("converts MB input to bytes", async () => {
+ const onSubmit = jest.fn();
+ renderWithFormik({
+ label: "Max File Size",
+ onSubmit
+ });
+
+ const field = screen.getByLabelText("Max File Size");
+ const submitButton = screen.getByText("submit");
+
+ await act(async () => {
+ await userEvent.clear(field); // field initializes with 0
+ await userEvent.type(field, "10");
+ await userEvent.click(submitButton);
+ });
+
+ expect(onSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ max_file_size: 10 * BYTES_PER_MB
+ }),
+ expect.anything()
+ );
+ });
+
+ it("displays bytes as MB", async () => {
+ const onSubmit = jest.fn();
+ renderWithFormik(
+ {
+ label: "Max File Size",
+ onSubmit
+ },
+ { max_file_size: 15_728_640 } // 15 * 1_048_576
+ );
+
+ const field = screen.getByLabelText("Max File Size");
+ expect(field).toHaveValue(15);
+ });
+ });
+});
diff --git a/src/components/mui/formik-inputs/mui-formik-file-size-field.js b/src/components/mui/formik-inputs/mui-formik-file-size-field.js
new file mode 100644
index 000000000..e6025bb6c
--- /dev/null
+++ b/src/components/mui/formik-inputs/mui-formik-file-size-field.js
@@ -0,0 +1,64 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { InputAdornment } from "@mui/material";
+import { useField } from "formik";
+import MuiFormikTextField from "./mui-formik-textfield";
+import { BYTES_PER_MB } from "../../../utils/constants";
+
+const BLOCKED_KEYS = ["e", "E", "+", "-", ".", ","];
+
+const MuiFormikFilesizeField = ({ name, label, ...props }) => {
+ const [field, meta, helpers] = useField(name);
+
+ const displayValue =
+ field.value != null ? Math.floor(field.value / BYTES_PER_MB) : 0;
+
+ const emptyValue = meta.initialValue === null ? null : 0;
+
+ const handleChange = (e) => {
+ const mbValue = e.target.value;
+
+ if (mbValue === "") {
+ helpers.setValue(emptyValue);
+ return;
+ }
+
+ const bytes = Number(mbValue) * BYTES_PER_MB;
+ helpers.setValue(bytes);
+ };
+
+ return (
+ MB
+ }
+ }}
+ onKeyDown={(e) => {
+ if (BLOCKED_KEYS.includes(e.key)) {
+ e.nativeEvent.preventDefault();
+ e.nativeEvent.stopImmediatePropagation();
+ }
+ }}
+ inputProps={{
+ min: 0,
+ inputMode: "numeric",
+ step: 1
+ }}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...props}
+ />
+ );
+};
+
+MuiFormikFilesizeField.propTypes = {
+ name: PropTypes.string.isRequired,
+ label: PropTypes.string
+};
+
+export default MuiFormikFilesizeField;
diff --git a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js
index 24269887a..60a2261f4 100644
--- a/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js
+++ b/src/pages/sponsors-global/page-templates/page-template-popup/modules/page-template-media-request-module.js
@@ -6,6 +6,7 @@ import { Grid2, Divider, InputLabel } from "@mui/material";
import MuiFormikTextField from "../../../../../components/mui/formik-inputs/mui-formik-textfield";
import MuiFormikDatepicker from "../../../../../components/mui/formik-inputs/mui-formik-datepicker";
import MuiFormikRadioGroup from "../../../../../components/mui/formik-inputs/mui-formik-radio-group";
+import MuiFormikFilesizeField from "../../../../../components/mui/formik-inputs/mui-formik-file-size-field";
import { PAGE_MODULES_MEDIA_TYPES } from "../../../../../utils/constants";
import MuiFormikAsyncAutocomplete from "../../../../../components/mui/formik-inputs/mui-formik-async-select";
import { queryMediaFileTypes } from "../../../../../actions/media-file-type-actions";
@@ -67,9 +68,8 @@ const MediaRequestModule = ({ baseName, index }) => {
{T.translate("page_template_list.page_crud.max_file_size")}
-
diff --git a/src/utils/constants.js b/src/utils/constants.js
index 738fefa11..3983fbeb5 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -163,6 +163,8 @@ export const LANGUAGE_CODE_LENGTH = 2;
export const SLICE_TICKET_NUMBER = -15;
+export const BYTES_PER_MB = 1_048_576; // 1024 * 1024
+
export const MARKETING_SETTING_TYPE_TEXT = "TEXT";
export const MARKETING_SETTING_TYPE_TEXTAREA = "TEXTAREA";
export const MARKETING_SETTING_TYPE_FILE = "FILE";
@@ -256,7 +258,7 @@ export const PURCHASE_STATUS = {
PENDING: "Pending",
PAID: "Paid",
CANCELLED: "Cancelled"
-}
+};
export const SPONSOR_USER_ASSIGNMENT_TYPE = {
EXISTING: "existing",