Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/components/mui/__tests__/mui-formik-file-size-field.test.js
Original file line number Diff line number Diff line change
@@ -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(
<Formik initialValues={initialValues} onSubmit={props.onSubmit}>
<Form>
<MuiFormikFilesizeField name="max_file_size" {...props} />
<button type="submit">submit</button>
</Form>
</Formik>
);

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);
});
});
});
64 changes: 64 additions & 0 deletions src/components/mui/formik-inputs/mui-formik-file-size-field.js
Original file line number Diff line number Diff line change
@@ -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 (
<MuiFormikTextField
name={name}
label={label}
type="number"
value={displayValue}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this? formik will pick up the value with the name

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is required to display a simpler value to the user (MB) instead of the value sent and received from the API (bytes)

onChange={handleChange}
slotProps={{
input: {
endAdornment: <InputAdornment position="end">MB</InputAdornment>
}
}}
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;
3 changes: 2 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
"url": "Wrong URL format",
"date": "Wrong date format.",
"after": "'{field1}' must be after '{field2}'.",
"boolean": "Must be a boolean."
"boolean": "Must be a boolean.",
"mib_aligned": "Must be a MiB aligned value"
Comment on lines +97 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Align MB/MiB wording with the UI label.

Line 98 says “MiB” while the UI label says “MB” (Line 3976). This mixed terminology can confuse users—please pick one term across label + validation.

📝 Suggested wording fix (if you keep “MB” in the label)
-    "mib_aligned": "Must be a MiB aligned value"
+    "mib_aligned": "Must be an MB-aligned value."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"boolean": "Must be a boolean.",
"mib_aligned": "Must be a MiB aligned value"
"boolean": "Must be a boolean.",
"mib_aligned": "Must be an MB-aligned value."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/i18n/en.json` around lines 97 - 98, The validation message key
"mib_aligned" uses "MiB" while the UI label uses "MB"; update the "mib_aligned"
value to match the UI terminology (e.g., change the string to "Must be an MB
aligned value") so labels and validation are consistent, and scan for any other
i18n keys using "MiB" to align them with the UI term if present.

},
"landing": {
"os_summit_admin": "Show Admin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as yup from "yup";
import MuiFormikTextField from "../../../../components/mui/formik-inputs/mui-formik-textfield";
import PageModules from "./page-template-modules-form";
import {
BYTES_PER_MB,
PAGES_MODULE_KINDS,
PAGE_MODULES_MEDIA_TYPES
} from "../../../../utils/constants";
Expand Down Expand Up @@ -88,6 +89,19 @@ const PageTemplatePopup = ({ pageTemplate, open, onClose, onSave }) => {
type: yup.string().required(T.translate("validation.required")),
upload_deadline: yup.date().required(T.translate("validation.required")),
description: yup.string().required(T.translate("validation.required")),
max_file_size: yup.number().when("type", {
is: PAGE_MODULES_MEDIA_TYPES.FILE,
then: (schema) =>
schema
.min(BYTES_PER_MB, T.translate("validation.number_positive"))
.required(T.translate("validation.required"))
.test(
"mib-aligned",
T.translate("validation.mib_aligned"),
(value) => value == null || value % BYTES_PER_MB === 0
),
otherwise: (schema) => schema.nullable()
}),
file_type_id: yup.object().when("type", {
is: PAGE_MODULES_MEDIA_TYPES.FILE,
then: (schema) => schema.required(T.translate("validation.required")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -67,9 +68,8 @@ const MediaRequestModule = ({ baseName, index }) => {
<InputLabel htmlFor={buildFieldName("max_file_size")}>
{T.translate("page_template_list.page_crud.max_file_size")}
</InputLabel>
<MuiFormikTextField
<MuiFormikFilesizeField
name={buildFieldName("max_file_size")}
type="number"
margin="none"
fullWidth
/>
Expand Down
4 changes: 3 additions & 1 deletion src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -256,7 +258,7 @@ export const PURCHASE_STATUS = {
PENDING: "Pending",
PAID: "Paid",
CANCELLED: "Cancelled"
}
};

export const SPONSOR_USER_ASSIGNMENT_TYPE = {
EXISTING: "existing",
Expand Down