Replace jalaali-react-date-picker with custom MUI-based Jalali date picker - fixes all 10 security vulnerabilities
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { DatePicker } from "jalaali-react-date-picker";
|
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
|
||||||
|
|
||||||
function DatePickerContent({ type, updateDate, handleClose }) {
|
function DatePickerContent({ type, updateDate, handleClose }) {
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<JalaliDatePicker
|
||||||
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"
|
className="!pt-[12px] !px-[12px] sm:!pt-[24px] sm:!px-[24px]"
|
||||||
dayLabelRender={(e) => (
|
dayLabelRender={() => (
|
||||||
<div className="day-label-bar-inner-rtl">
|
<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">شنبه</div>
|
||||||
<div className="day-label-item !text-[10px] font-semibold">
|
<div className="day-label-item !text-[10px] font-semibold">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { DatePicker } from "jalaali-react-date-picker";
|
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
|
||||||
import { nextIconData, prevIconData } from "./dataIcon";
|
import { nextIconData, prevIconData } from "./dataIcon";
|
||||||
import LoadingDate from "../LoadingDate";
|
import LoadingDate from "../LoadingDate";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -16,10 +16,9 @@ function FirstDatePicker({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full first md:w-1/2 lg:w-full xl:w-1/2 relative flex justify-center">
|
<div className="w-full first md:w-1/2 lg:w-full xl:w-1/2 relative flex justify-center">
|
||||||
<DatePicker
|
<JalaliDatePicker
|
||||||
value={startDate}
|
value={startDate}
|
||||||
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
|
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
|
||||||
timePicker={false}
|
|
||||||
nextIcon={
|
nextIcon={
|
||||||
<Image {...nextIconData} ref={nextIconDatePickerF} id="nextIconF" />
|
<Image {...nextIconData} ref={nextIconDatePickerF} id="nextIconF" />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { DatePicker } from "jalaali-react-date-picker";
|
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
|
||||||
import { nextIconData, prevIconData } from "./dataIcon";
|
import { nextIconData, prevIconData } from "./dataIcon";
|
||||||
import LoadingDate from "../LoadingDate";
|
import LoadingDate from "../LoadingDate";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
@@ -16,9 +16,8 @@ function SecondDatePicker({
|
|||||||
return (
|
return (
|
||||||
<div className="w-1/2 second hidden md:flex lg:hidden xl:flex">
|
<div className="w-1/2 second hidden md:flex lg:hidden xl:flex">
|
||||||
<div className="w-full relative flex justify-center">
|
<div className="w-full relative flex justify-center">
|
||||||
<DatePicker
|
<JalaliDatePicker
|
||||||
value={endDate}
|
value={endDate}
|
||||||
timePicker={false}
|
|
||||||
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
|
className={`w-full ${loading ? "opacity-0" : "opacity-100"}`}
|
||||||
nextIcon={
|
nextIcon={
|
||||||
<Image
|
<Image
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import ThemeRegistry from "./component/ThemeRegistry";
|
import ThemeRegistry from "./component/ThemeRegistry";
|
||||||
import "jalaali-react-date-picker/lib/styles/index.css";
|
|
||||||
import { Providers } from "./Providers";
|
import { Providers } from "./Providers";
|
||||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||||
import { GoogleTagManager } from "@next/third-parties/google";
|
import { GoogleTagManager } from "@next/third-parties/google";
|
||||||
|
|||||||
@@ -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 CalendarEdit from "@/components/icons/CalendarEdit";
|
||||||
import { convertToJalali } from "@/helper";
|
import { convertToJalali } from "@/helper";
|
||||||
import { Button, Popover } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
import { DatePicker } from "jalaali-react-date-picker";
|
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
function DateBirthday({ data, changeData, error }) {
|
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 (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div
|
<div className="relative">
|
||||||
className={`relative w-full border cursor-pointer py-[15px] px-[16px] border-solid
|
<JalaliDatePicker
|
||||||
rounded-[8px] h-[48px]
|
value={data}
|
||||||
${error ? "border-[#ff0000]" : "border-[#D7D7D7]"}
|
placeholder="تاریخ تولد"
|
||||||
`}
|
error={error}
|
||||||
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"
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
changeData(convertToJalali(e._d), "birthday");
|
changeData(convertToJalali(e._d), "birthday");
|
||||||
handleClose();
|
}}
|
||||||
|
textFieldProps={{
|
||||||
|
InputProps: {
|
||||||
|
endAdornment: (
|
||||||
|
<Button className="!min-w-fit !p-1">
|
||||||
|
<CalendarEdit />
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { DatePicker } from "jalaali-react-date-picker";
|
import JalaliDatePicker from "@/components/common/JalaliDatePicker";
|
||||||
|
|
||||||
function Date() {
|
function Date() {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center sm-datepicker active-today jalali-dashboard">
|
<div className="flex justify-center sm-datepicker active-today jalali-dashboard">
|
||||||
<DatePicker
|
<JalaliDatePicker
|
||||||
nextIcon={<></>}
|
nextIcon={<></>}
|
||||||
prevIcon={<></>}
|
prevIcon={<></>}
|
||||||
dayLabelRender={(e) => (
|
dayLabelRender={() => (
|
||||||
<div className="day-label-bar-inner-rtl">
|
<div className="day-label-bar-inner-rtl">
|
||||||
<div className="day-label-item !text-[10px] font-semibold">
|
<div className="day-label-item !text-[10px] font-semibold">
|
||||||
شنبه
|
شنبه
|
||||||
|
|||||||
Generated
+69
-15969
File diff suppressed because it is too large
Load Diff
+1
-2
@@ -28,7 +28,6 @@
|
|||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"jalaali-js": "^1.2.8",
|
"jalaali-js": "^1.2.8",
|
||||||
"jalaali-react-date-picker": "^1.0.18",
|
|
||||||
"jalali-moment": "^3.3.11",
|
"jalali-moment": "^3.3.11",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jspdf": "^3.0.4",
|
"jspdf": "^3.0.4",
|
||||||
@@ -61,4 +60,4 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user