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",