diff --git a/package-lock.json b/package-lock.json index a314f83..68ff385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^15.2.0", + "react-loading": "^2.0.3", + "react-multi-date-picker": "^4.5.2", "react-router-dom": "^7.1.0", "react-toastify": "^11.0.2", "tailwind-merge": "^2.5.5" @@ -2905,7 +2907,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -3035,6 +3036,19 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3168,7 +3182,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3505,6 +3518,18 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3545,6 +3570,12 @@ "node": ">=0.10.0" } }, + "node_modules/react-date-object": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz", + "integrity": "sha512-BHxD/quWOTo9fLKV/cfL/M31ePoj4a1JaJ/CnOf8Ndg3mrkh4x9wEMMkCfTrzduxDOgU8ZgR8uarhqI5G71sTg==", + "license": "MIT" + }, "node_modules/react-dom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", @@ -3557,6 +3588,16 @@ "react": "^19.0.0" } }, + "node_modules/react-element-popper": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.7.tgz", + "integrity": "sha512-tuM2OxKlW32h+6uFSK6EENHPeZ2OGgOipHfOAl+VLWEv9/j3QkSGbD+ADX3A9uJlmq24i37n28RjJmAbGTfpEg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-i18next": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.2.0.tgz", @@ -3579,6 +3620,37 @@ } } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/react-loading": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz", + "integrity": "sha512-Vdqy79zq+bpeWJqC+xjltUjuGApyoItPgL0vgVfcJHhqwU7bAMKzysfGW/ADu6i0z0JiOCRJjo+IkFNkRNbA3A==", + "license": "MIT", + "peerDependencies": { + "prop-types": "^15.6.0", + "react": ">=0.14.0" + } + }, + "node_modules/react-multi-date-picker": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz", + "integrity": "sha512-FgWjZB3Z6IA6XpcWiLPk85PwcRUhOiYhKK42o5k672gD/n2I6rzPfQ8bUrldOIiF/Z7FfOCdH7a6FeubzqteLg==", + "license": "MIT", + "dependencies": { + "react-date-object": "^2.1.8", + "react-element-popper": "^2.1.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", diff --git a/package.json b/package.json index 8ca0cd3..3d5c5a3 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-i18next": "^15.2.0", + "react-loading": "^2.0.3", + "react-multi-date-picker": "^4.5.2", "react-router-dom": "^7.1.0", "react-toastify": "^11.0.2", "tailwind-merge": "^2.5.5" diff --git a/src/assets/images/calendar.svg b/src/assets/images/calendar.svg new file mode 100644 index 0000000..63391d1 --- /dev/null +++ b/src/assets/images/calendar.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..d46d9a1 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,32 @@ +import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react' +import ReactLoading from 'react-loading' +import { XOR } from '../helpers/types' +import { clx } from '../helpers/utils' + +type Props = { + className?: string; + isLoading?: boolean, +} & ButtonHTMLAttributes & + XOR<{ children: ReactNode }, { label: string, isLoading?: boolean }>; + +const Button: FC = memo((props: Props) => { + + const buttonClass = clx( + 'flex rounded-xl items-center justify-center text-center h-12 text-sm bg-primary text-white w-full', + props.disabled && 'cursor-not-allowed opacity-60', + props.className + ); + + return ( + + { + props.isLoading ? + + : + props.label || props.children + } + + ) +}) + +export default Button \ No newline at end of file diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx new file mode 100644 index 0000000..6e2f994 --- /dev/null +++ b/src/components/DatePicker.tsx @@ -0,0 +1,73 @@ +import { useState, useEffect, FC } from 'react'; +import DatePicker from 'react-multi-date-picker'; +import persian from 'react-date-object/calendars/persian'; +import persian_fa from 'react-date-object/locales/persian_fa'; +import DateObject from 'react-date-object'; +import CalenderIcon from '../assets/images/calendar.svg' + +type Props = { + onChange: (date: string) => void; + defaulValue?: string; + error_text?: string; + placeholder: string; + reset?: boolean; + isDateTime?: boolean; + className?: string; + label: string +}; + +const DatePickerComponent: FC = (props: Props) => { + const [value, setValue] = useState(null); + + useEffect(() => { + if (props.reset) { + setValue(null); + } + }, [props.reset]); + + useEffect(() => { + if (value) { + const formattedDate = `${value.year}/${value.month.number}/${value.day}`; + props.onChange(formattedDate); + } + }, [props, value]); + + useEffect(() => { + if (props.defaulValue && !value) { + const defaultDate = new DateObject({ + date: props.defaulValue, + calendar: persian, + locale: persian_fa, + }); + setValue(defaultDate); + } + }, [props.defaulValue, value]); + + return ( + + + {props.label} + + + setValue(date as DateObject)} + calendar={persian} + locale={persian_fa} + calendarPosition="bottom-right" + className={props.className} + /> + {props.error_text && props.error_text !== '' && ( + + {props.error_text} + + )} + + + + + ); +}; + +export default DatePickerComponent; \ No newline at end of file diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 93e2f9e..15b6188 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -17,7 +17,7 @@ type Props = { const Input: FC = (props: Props) => { const inputClass = clx( - 'w-full h-12 text-black block px-4 rounded-xl mt-2 border border-border', + 'w-full h-12 text-black block px-4 text-sm rounded-xl mt-1 border border-border', props.readOnly && 'bg-gray-100', props.className ); diff --git a/src/index.css b/src/index.css index af6e342..4baf4e8 100644 --- a/src/index.css +++ b/src/index.css @@ -35,3 +35,15 @@ textarea::placeholder { @apply flex flex-col sm:flex-row sm:gap-5 gap-5; } } +.rmdp-input { + min-height: 48px; + border-radius: 12px !important; + border: 1px solid #d0d0d0 !important; + font-size: 14px !important; + width: 100% !important; + margin: 0px; + padding-right: 16px !important; +} +.rmdp-container { + width: 100%; +} diff --git a/src/langs/fa.json b/src/langs/fa.json index 92be903..e3c7ba9 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -6,6 +6,16 @@ "name": "نام", "enter_name": "نام خود را وارد کنید", "family": "نام خانوادگی", - "enter_family": "نام خانوادگی خود را وارد کنید" + "enter_family": "نام خانوادگی خود را وارد کنید", + "select_date": "انتخاب تاریخ", + "dateofbirth": "تاریخ تولد", + "national_code": "کد ملی", + "email": "ایمیل", + "enter_email": "ایمیل خود را وارد کنید", + "phonen_number": "شماره تماس", + "enter_phone_number": "شماره تماس خود را وارد کنید", + "password": "رمز عبور", + "enter_password": "رمز عبور انتخابی خود را وارد کنید", + "next": "ادامه" } } diff --git a/src/pages/auth/Login.tsx b/src/pages/auth/Login.tsx index 973eda9..df01a2f 100644 --- a/src/pages/auth/Login.tsx +++ b/src/pages/auth/Login.tsx @@ -3,15 +3,17 @@ import LogoImage from '../../assets/images/logo.svg' import LogoSmallImage from '../../assets/images/logo-small.svg' import { useTranslation } from 'react-i18next' import Input from '../../components/Input' +import DatePickerComponent from '../../components/DatePicker' +import Button from '../../components/Button' const Login: FC = () => { const { t } = useTranslation('global') return ( - + - + @@ -37,6 +39,38 @@ const Login: FC = () => { placeholder={t('auth.enter_family')} /> + + console.log(date)} placeholder={t('auth.select_date')} /> + + + + + + + + + + + + + +