From 22482aa20a298b1197d1ea304d51aa88f2632566 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 26 Jan 2025 15:45:46 +0330 Subject: [PATCH] category service --- .env | 4 +- package-lock.json | 112 +++++++++++- package.json | 2 + src/App.tsx | 1 + src/assets/images/logo_orig.svg | 45 +++++ src/components/Button.tsx | 8 +- src/components/Input.tsx | 26 ++- src/components/PageLoading.tsx | 19 ++ src/components/Pagination.tsx | 99 +++++++++++ src/components/Select.tsx | 7 +- src/config/axios.ts | 27 +++ src/config/func.ts | 4 + src/helpers/types.ts | 16 +- src/index.css | 5 + src/langs/fa.json | 8 +- src/pages/auth/Login.tsx | 12 +- src/pages/auth/Register.tsx | 66 ++----- src/pages/auth/components/LoginStep1.tsx | 43 +++-- src/pages/auth/components/LoginStep2.tsx | 51 ++++-- src/pages/auth/components/LoginStep3.tsx | 27 ++- src/pages/auth/components/RegisterStep1.tsx | 77 ++++++++ src/pages/auth/components/RegisterStep2.tsx | 139 +++++++++++++++ src/pages/auth/hooks/useAuthData.tsx | 39 ++++ src/pages/auth/service/AuthService.ts | 39 ++++ src/pages/auth/store/AuthStore.ts | 4 + src/pages/auth/types/AuthTypes.ts | 38 ++++ src/pages/service/Category.tsx | 168 ++++++++---------- .../service/components/CreateCategory.tsx | 123 +++++++++++++ .../service/components/StatusCategory.tsx | 28 +++ src/pages/service/hooks/useServiceData.ts | 44 +++++ src/pages/service/service/ServiceService.ts | 39 ++++ src/pages/service/types/ServiceTypes.ts | 27 +++ src/router/Auth.tsx | 2 - src/shared/SideBar.tsx | 1 + 34 files changed, 1146 insertions(+), 204 deletions(-) create mode 100644 src/assets/images/logo_orig.svg create mode 100644 src/components/PageLoading.tsx create mode 100644 src/components/Pagination.tsx create mode 100644 src/config/axios.ts create mode 100644 src/config/func.ts create mode 100644 src/pages/auth/components/RegisterStep1.tsx create mode 100644 src/pages/auth/components/RegisterStep2.tsx create mode 100644 src/pages/auth/hooks/useAuthData.tsx create mode 100644 src/pages/auth/service/AuthService.ts create mode 100644 src/pages/service/components/CreateCategory.tsx create mode 100644 src/pages/service/components/StatusCategory.tsx create mode 100644 src/pages/service/hooks/useServiceData.ts create mode 100644 src/pages/service/service/ServiceService.ts create mode 100644 src/pages/service/types/ServiceTypes.ts diff --git a/.env b/.env index 2fd9f31..4b17138 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ -VITE_TOKEN_NAME = 'admin_token' \ No newline at end of file +VITE_TOKEN_NAME = 'admin_token' +VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token' +VITE_BASE_URL = 'http://192.168.0.223:3500' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d1bc9ef..1ef9c14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@headlessui/react": "^2.2.0", "@material-tailwind/react": "^2.1.10", "@tanstack/react-query": "^5.62.8", + "axios": "^1.7.9", "draft-js": "^0.11.7", "formik": "^2.4.6", "i18next": "^24.2.0", @@ -25,6 +26,7 @@ "react-multi-date-picker": "^4.5.2", "react-otp-input": "^3.1.1", "react-router-dom": "^7.1.0", + "react-spinners": "^0.15.0", "react-toastify": "^11.0.2", "swiper": "^11.2.1", "tailwind-merge": "^2.5.5", @@ -2259,6 +2261,12 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/attr-accept": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", @@ -2306,6 +2314,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2514,6 +2533,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2635,6 +2666,15 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -3096,6 +3136,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", @@ -3113,6 +3173,20 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formik": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", @@ -3401,7 +3475,6 @@ "version": "3.7.6", "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz", "integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.8.0" @@ -3748,6 +3821,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4247,6 +4341,12 @@ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4517,6 +4617,16 @@ "react-dom": ">=18" } }, + "node_modules/react-spinners": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.15.0.tgz", + "integrity": "sha512-ZO3/fNB9Qc+kgpG3SfdlMnvTX6LtLmTnOogb3W6sXIaU/kZ1ydEViPfZ06kSOaEsor58C/tzXw2wROGQu3X2pA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-toastify": { "version": "11.0.2", "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.2.tgz", diff --git a/package.json b/package.json index 066e8a8..655e1ed 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@headlessui/react": "^2.2.0", "@material-tailwind/react": "^2.1.10", "@tanstack/react-query": "^5.62.8", + "axios": "^1.7.9", "draft-js": "^0.11.7", "formik": "^2.4.6", "i18next": "^24.2.0", @@ -27,6 +28,7 @@ "react-multi-date-picker": "^4.5.2", "react-otp-input": "^3.1.1", "react-router-dom": "^7.1.0", + "react-spinners": "^0.15.0", "react-toastify": "^11.0.2", "swiper": "^11.2.1", "tailwind-merge": "^2.5.5", diff --git a/src/App.tsx b/src/App.tsx index f69517b..950f6f2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import { IApiErrorRepsonse } from './types/error.types' import MainRouter from './router/Main' import AuthRouter from './router/Auth' import { Pages } from './config/Pages' + i18next.init({ interpolation: { escapeValue: false }, lng: 'fa', diff --git a/src/assets/images/logo_orig.svg b/src/assets/images/logo_orig.svg new file mode 100644 index 0000000..a764edd --- /dev/null +++ b/src/assets/images/logo_orig.svg @@ -0,0 +1,45 @@ + \ No newline at end of file diff --git a/src/components/Button.tsx b/src/components/Button.tsx index fb4fc65..c314f47 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -1,5 +1,5 @@ import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react' -import ReactLoading from 'react-loading' +import MoonLoader from "react-spinners/MoonLoader" import { XOR } from '../helpers/types' import { clx } from '../helpers/utils' @@ -7,7 +7,7 @@ type Props = { className?: string; isLoading?: boolean, } & ButtonHTMLAttributes & - XOR<{ children: ReactNode }, { label: string, isLoading?: boolean }>; + XOR<{ children: ReactNode }, { label: string }>; const Button: FC = memo((props: Props) => { @@ -18,10 +18,10 @@ const Button: FC = memo((props: Props) => { ); return ( - */} + + {/* شماره صفحات */} + {pageNumbers.map((page, index) => + typeof page === "number" ? ( + + ) : ( + + {page} + + ) + )} + + {/* دکمه بعدی */} + {/* */} + + ); +}; + +export default Pagination; \ No newline at end of file diff --git a/src/components/Select.tsx b/src/components/Select.tsx index d42a569..115ecca 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -1,6 +1,7 @@ import { FC, SelectHTMLAttributes } from 'react' import { clx } from '../helpers/utils' import { ArrowDown2 } from 'iconsax-react' +import Error from './Error' export type ItemsSelectType = { value: string, @@ -48,9 +49,9 @@ const Select: FC = (props: Props) => { )} /> { props.error_text && props.error_text !== '' ? -
- {props.error_text} -
+ : null } diff --git a/src/config/axios.ts b/src/config/axios.ts new file mode 100644 index 0000000..06cc68c --- /dev/null +++ b/src/config/axios.ts @@ -0,0 +1,27 @@ +import axios from "axios"; + +const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_BASE_URL, +}); + +axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response.status === 401) { + localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME); + window.location.href = "/auth/login"; + } + return Promise.reject(error); + } +); + +axiosInstance.interceptors.request.use(async function (config) { + const token = localStorage.getItem(import.meta.env.VITE_TOKEN_NAME) + ? localStorage.getItem(import.meta.env.VITE_TOKEN_NAME) + : ""; + + config.headers.Authorization = "Bearer " + token; + return config; +}); + +export default axiosInstance; diff --git a/src/config/func.ts b/src/config/func.ts new file mode 100644 index 0000000..c16ba63 --- /dev/null +++ b/src/config/func.ts @@ -0,0 +1,4 @@ +export function isEmail(input: string): boolean { + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; + return emailRegex.test(input); +} diff --git a/src/helpers/types.ts b/src/helpers/types.ts index bb0dd63..d8f7039 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -5,6 +5,16 @@ export type XOR = T | U extends object : T | U; export type ItemsSelectType = { - value: any, - label: string, -} \ No newline at end of file + value: string; + label: string; +}; + +export type ErrorType = Error & { + response?: { + data?: { + error: { + message: string[]; + }; + }; + }; +}; diff --git a/src/index.css b/src/index.css index e9202e9..0c87a3b 100644 --- a/src/index.css +++ b/src/index.css @@ -145,3 +145,8 @@ tbody tr { .ql-toolbar:first-child { display: none !important; } + +.Toastify__toast { + direction: rtl; + font-family: "irancell" !important; +} diff --git a/src/langs/fa.json b/src/langs/fa.json index 1242e9f..36f8b37 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -151,7 +151,9 @@ "status_category": "وضعیت دسته بندی", "title_category": "عنوان دسته بندی", "category_parent_2": "دسته بندی مادر", - "icon_serice_category": "آیکون سرویس مورد نظر را آپلود کنید" + "icon_serice_category": "آیکون سرویس مورد نظر را آپلود کنید", + "active": "فعال", + "deactive": "غیرفعال" }, "save": "ذخیره", "search": "جستجو", @@ -481,5 +483,7 @@ "email": "ایمیل", "subject": "موضوع", "date": "تاریخ" - } + }, + "loading": "در حال بارگذاری...", + "success": "عملیات با موفقیت انجام شد." } diff --git a/src/pages/auth/Login.tsx b/src/pages/auth/Login.tsx index 04c9063..13875fe 100644 --- a/src/pages/auth/Login.tsx +++ b/src/pages/auth/Login.tsx @@ -8,7 +8,9 @@ import LoginStep3 from './components/LoginStep3' const Login: FC = () => { - const { stepLogin } = useAuthStore() + const { stepLogin, phone, email } = useAuthStore() + + console.log(phone, email, stepLogin); return ( @@ -23,11 +25,13 @@ const Login: FC = () => { stepLogin === 1 ? : - stepLogin === 2 ? + stepLogin === 2 && !!phone ? - : stepLogin === 3 ? + : stepLogin === 2 && !!email ? - : null + : stepLogin === 3 ? + + : null } diff --git a/src/pages/auth/Register.tsx b/src/pages/auth/Register.tsx index afda833..4ddf1c8 100644 --- a/src/pages/auth/Register.tsx +++ b/src/pages/auth/Register.tsx @@ -2,20 +2,23 @@ import { FC } from 'react' 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' import { Link } from 'react-router-dom' import { Pages } from '../../config/Pages' +import RegisterStep1 from './components/RegisterStep1' +import { useAuthStore } from './store/AuthStore' +import RegisterStep2 from './components/RegisterStep2' + const Register: FC = () => { + const { stepLogin } = useAuthStore() const { t } = useTranslation('global') + return (
-
+
@@ -27,55 +30,12 @@ const Register: FC = () => {

- {/*
- {t('auth.user_info')} -
*/} - -
- - - - -
-
- console.log(date)} placeholder={t('auth.select_date')} /> - - - -
- -
- - -
- -
- -
- -
+ ) +} + +export default RegisterStep1 \ No newline at end of file diff --git a/src/pages/auth/components/RegisterStep2.tsx b/src/pages/auth/components/RegisterStep2.tsx new file mode 100644 index 0000000..483bec2 --- /dev/null +++ b/src/pages/auth/components/RegisterStep2.tsx @@ -0,0 +1,139 @@ +import { FC, Fragment } from 'react' +import { useFormik } from 'formik' +import { RegisterType } from '../types/AuthTypes' +import * as Yup from 'yup' +import { useAuthStore } from '../store/AuthStore' +import { useTranslation } from 'react-i18next' +import Input from '../../../components/Input' +import DatePickerComponent from '../../../components/DatePicker' +import Button from '../../../components/Button' +import { useRegister } from '../hooks/useAuthData' +import { ErrorType } from '../../../helpers/types' +import { toast } from 'react-toastify' +import { Pages } from '../../../config/Pages' + +const RegisterStep2: FC = () => { + + const { t } = useTranslation('global') + const { phone } = useAuthStore() + const register = useRegister() + + const formik = useFormik({ + initialValues: { + firstName: '', + lastName: '', + birthDate: '', + code: 0, + nationalCode: '', + password: '', + phone: phone, + }, + validationSchema: Yup.object({ + firstName: Yup.string() + .required(t('errors.required')), + lastName: Yup.string() + .required(t('errors.required')), + birthDate: Yup.string() + .required(t('errors.required')), + code: Yup.number() + .required(t('errors.required')), + nationalCode: Yup.string() + .required(t('errors.required')), + password: Yup.string() + .required(t('errors.required')), + phone: Yup.string() + .required(t('errors.required')), + }), + onSubmit(values) { + register.mutate(values, { + onSuccess(data) { + localStorage.setItem(import.meta.env.VITE_TOKEN_NAME, data?.data?.accessToken?.token) + localStorage.setItem(import.meta.env.VITE_REFRESH_TOKEN_NAME, data?.data?.refreshToken?.token) + window.location.href = Pages.dashboard + }, + onError(error: ErrorType) { + toast.error(error?.response?.data?.error?.message[0]) + }, + }) + }, + }) + + console.log(formik.errors); + + + return ( + +
+ + + + +
+
+ formik.setFieldValue('birthDate', date)} + placeholder={t('auth.select_date')} + error_text={formik.touched.birthDate && formik.errors.birthDate ? formik.errors.birthDate : ''} + /> + + +
+ +
+ + +
+ +
+ +
+ +