Replace jalaali-react-date-picker with custom MUI-based Jalali date picker - fixes all 10 security vulnerabilities

This commit is contained in:
hamed
2025-12-17 10:09:37 +03:30
parent e5e6f33ae4
commit db2f9aef60
9 changed files with 409 additions and 16042 deletions
+4 -4
View File
@@ -1,10 +1,10 @@
import { DatePicker } from "jalaali-react-date-picker";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
function DatePickerContent({ type, updateDate, handleClose }) {
return (
<DatePicker
className="!pt-[12px] !px-[12px] sm:!pt-[24px] sm:!px-[24px] !border !border-solid !border-[#EFEFEF] dark:!border-none !bg-[#FFF] !rounded-[8px] !overflow-hidden"
dayLabelRender={(e) => (
<JalaliDatePicker
className="!pt-[12px] !px-[12px] sm:!pt-[24px] sm:!px-[24px]"
dayLabelRender={() => (
<div className="day-label-bar-inner-rtl">
<div className="day-label-item !text-[10px] font-semibold">شنبه</div>
<div className="day-label-item !text-[10px] font-semibold">
@@ -1,5 +1,5 @@
import React from "react";
import { DatePicker } from "jalaali-react-date-picker";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
import { nextIconData, prevIconData } from "./dataIcon";
import LoadingDate from "../LoadingDate";
import Image from "next/image";
@@ -16,10 +16,9 @@ function FirstDatePicker({
}) {
return (
<div className="w-full first md:w-1/2 lg:w-full xl:w-1/2 relative flex justify-center">
<DatePicker
<JalaliDatePicker
value={startDate}
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
timePicker={false}
nextIcon={
<Image {...nextIconData} ref={nextIconDatePickerF} id="nextIconF" />
}
@@ -1,5 +1,5 @@
import React from "react";
import { DatePicker } from "jalaali-react-date-picker";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
import { nextIconData, prevIconData } from "./dataIcon";
import LoadingDate from "../LoadingDate";
import Image from "next/image";
@@ -16,9 +16,8 @@ function SecondDatePicker({
return (
<div className="w-1/2 second hidden md:flex lg:hidden xl:flex">
<div className="w-full relative flex justify-center">
<DatePicker
<JalaliDatePicker
value={endDate}
timePicker={false}
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
nextIcon={
<Image
-1
View File
@@ -1,6 +1,5 @@
import "./globals.css";
import ThemeRegistry from "./component/ThemeRegistry";
import "jalaali-react-date-picker/lib/styles/index.css";
import { Providers } from "./Providers";
import { GoogleAnalytics } from "@next/third-parties/google";
import { GoogleTagManager } from "@next/third-parties/google";
+311
View File
@@ -0,0 +1,311 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { TextField, Popover, IconButton, Box } from "@mui/material";
import moment from "moment-jalaali";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
moment.loadPersian({ dialect: "persian-modern", usePersianDigits: false });
const WEEKDAYS = ["ش", "ی", "د", "س", "چ", "پ", "ج"];
const MONTHS = [
"فروردین",
"اردیبهشت",
"خرداد",
"تیر",
"مرداد",
"شهریور",
"مهر",
"آبان",
"آذر",
"دی",
"بهمن",
"اسفند",
];
function JalaliDatePicker({
value,
onChange,
label,
placeholder = "انتخاب تاریخ",
error = false,
helperText = "",
disabled = false,
className = "",
textFieldProps = {},
disabledDates = null,
nextIcon = null,
prevIcon = null,
dayLabelRender = null,
isFirst = false,
}) {
const [anchorEl, setAnchorEl] = useState(null);
const [selectedDate, setSelectedDate] = useState(
value ? moment(value) : moment()
);
const [displayDate, setDisplayDate] = useState(
value ? moment(value) : moment()
);
const nextIconRef = useRef(null);
const prevIconRef = useRef(null);
useEffect(() => {
if (value) {
const newDate = moment(value);
setSelectedDate(newDate);
setDisplayDate(newDate);
}
}, [value]);
const handleClick = (event) => {
if (!disabled) {
setAnchorEl(event.currentTarget);
}
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const handleDateClick = (day) => {
const newDate = moment(displayDate)
.jDate(day)
.jMonth(displayDate.jMonth())
.jYear(displayDate.jYear());
if (disabledDates && disabledDates(newDate)) {
return;
}
setSelectedDate(newDate);
if (onChange) {
onChange({ _d: newDate.toDate() });
}
handleClose();
};
const handlePrevMonth = () => {
setDisplayDate(moment(displayDate).subtract(1, "jMonth"));
};
const handleNextMonth = () => {
setDisplayDate(moment(displayDate).add(1, "jMonth"));
};
const getDaysInMonth = () => {
const year = displayDate.jYear();
const month = displayDate.jMonth();
const daysInMonth = moment.jDaysInMonth(year, month);
const firstDayOfMonth = moment([year, month, 1], "jYYYY/jMM/jDD").day();
const days = [];
const startOffset = (firstDayOfMonth + 1) % 7;
for (let i = 0; i < startOffset; i++) {
days.push(null);
}
for (let day = 1; day <= daysInMonth; day++) {
days.push(day);
}
return days;
};
const formatDisplayValue = () => {
if (!selectedDate) return "";
return selectedDate.format("jYYYY/jMM/jDD");
};
const days = getDaysInMonth();
return (
<div className={className}>
<TextField
fullWidth
value={formatDisplayValue()}
onClick={handleClick}
placeholder={placeholder}
label={label}
error={error}
helperText={helperText}
disabled={disabled}
InputProps={{
readOnly: true,
}}
{...textFieldProps}
/>
<Popover
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
transformOrigin={{
vertical: "top",
horizontal: "left",
}}
sx={{
"& .MuiPaper-root": {
borderRadius: "8px",
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.1)",
overflow: "hidden",
},
}}
>
<Box
sx={{
width: 320,
p: 2,
bgcolor: "background.paper",
}}
>
{/* Header */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
{nextIcon ? (
<Box
ref={nextIconRef}
onClick={handleNextMonth}
sx={{ cursor: "pointer" }}
>
{nextIcon}
</Box>
) : (
<IconButton onClick={handleNextMonth} size="small">
<ChevronLeftIcon />
</IconButton>
)}
<Box sx={{ fontWeight: 600, fontSize: "14px" }}>
{MONTHS[displayDate.jMonth()]} {displayDate.jYear()}
</Box>
{prevIcon ? (
<Box
ref={prevIconRef}
onClick={handlePrevMonth}
sx={{ cursor: "pointer" }}
>
{prevIcon}
</Box>
) : (
<IconButton onClick={handlePrevMonth} size="small">
<ChevronRightIcon />
</IconButton>
)}
</Box>
{/* Weekday labels */}
<Box
sx={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
gap: 0.5,
mb: 1,
}}
>
{dayLabelRender ? (
dayLabelRender()
) : (
<>
{WEEKDAYS.map((day, index) => (
<Box
key={index}
sx={{
textAlign: "center",
fontSize: "12px",
fontWeight: 600,
color: "text.secondary",
py: 0.5,
}}
>
{day}
</Box>
))}
</>
)}
</Box>
{/* Days grid */}
<Box
sx={{
display: "grid",
gridTemplateColumns: "repeat(7, 1fr)",
gap: 0.5,
}}
>
{days.map((day, index) => {
if (!day) {
return <Box key={index} />;
}
const dayDate = moment(displayDate)
.jDate(day)
.jMonth(displayDate.jMonth())
.jYear(displayDate.jYear());
const isSelected =
selectedDate &&
dayDate.jYear() === selectedDate.jYear() &&
dayDate.jMonth() === selectedDate.jMonth() &&
dayDate.jDate() === selectedDate.jDate();
const isToday =
dayDate.jYear() === moment().jYear() &&
dayDate.jMonth() === moment().jMonth() &&
dayDate.jDate() === moment().jDate();
const isDisabled = disabledDates && disabledDates(dayDate);
return (
<Box
key={index}
onClick={() => !isDisabled && handleDateClick(day)}
sx={{
textAlign: "center",
py: 1,
cursor: isDisabled ? "not-allowed" : "pointer",
borderRadius: "4px",
fontSize: "14px",
bgcolor: isSelected ? "primary.main" : "transparent",
color: isSelected
? "primary.contrastText"
: isDisabled
? "text.disabled"
: "text.primary",
border: isToday && !isSelected ? "1px solid" : "none",
borderColor: "primary.main",
opacity: isDisabled ? 0.3 : 1,
"&:hover": {
bgcolor: isDisabled
? "transparent"
: isSelected
? "primary.dark"
: "action.hover",
},
}}
>
{day}
</Box>
);
})}
</Box>
</Box>
</Popover>
</div>
);
}
export default JalaliDatePicker;
@@ -1,70 +1,30 @@
import CalendarEdit from "@/components/icons/CalendarEdit";
import { convertToJalali } from "@/helper";
import { Button, Popover } from "@mui/material";
import { DatePicker } from "jalaali-react-date-picker";
import { useState } from "react";
import { Button } from "@mui/material";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
function DateBirthday({ data, changeData, error }) {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div className="w-full">
<div
className={`relative w-full border cursor-pointer py-[15px] px-[16px] border-solid
rounded-[8px] h-[48px]
${error ? "border-[#ff0000]" : "border-[#D7D7D7]"}
`}
onClick={handleClick}
>
<p className="text-[#7E7E7E] text-[14px] font-normal cursor-pointer">
{data}
</p>
<Button className="!absolute !left-[8px] !top-1/2 !-translate-y-1/2 !min-w-fit !p-1">
<CalendarEdit />
</Button>
</div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
sx={{
"& .MuiPaper-root": {
padding: "12px",
background: "#FAFAFA !important",
borderRadius: "16px",
border: "1px solid #E9ECEF",
boxShadow: "0px 2px 28.4px 0px rgba(112, 112, 112, 0.28)",
},
}}
>
<DatePicker
locale="fa"
<div className="relative">
<JalaliDatePicker
value={data}
placeholder="تاریخ تولد"
error={error}
onChange={(e) => {
changeData(convertToJalali(e._d), "birthday");
handleClose();
}}
textFieldProps={{
InputProps: {
endAdornment: (
<Button className="!min-w-fit !p-1">
<CalendarEdit />
</Button>
),
},
}}
/>
</Popover>
</div>
</div>
);
}
+3 -3
View File
@@ -1,14 +1,14 @@
"use client";
import { DatePicker } from "jalaali-react-date-picker";
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
function Date() {
return (
<div className="flex justify-center sm-datepicker active-today jalali-dashboard">
<DatePicker
<JalaliDatePicker
nextIcon={<></>}
prevIcon={<></>}
dayLabelRender={(e) => (
dayLabelRender={() => (
<div className="day-label-bar-inner-rtl">
<div className="day-label-item !text-[10px] font-semibold">
شنبه
+69 -15969
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -28,7 +28,6 @@
"html2canvas": "^1.4.1",
"i": "^0.3.7",
"jalaali-js": "^1.2.8",
"jalaali-react-date-picker": "^1.0.18",
"jalali-moment": "^3.3.11",
"js-cookie": "^3.0.5",
"jspdf": "^3.0.4",
@@ -61,4 +60,4 @@
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
}