diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 3e91500..c0039a8 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -150,5 +150,7 @@ export const Pages = { detail: "/orders/native/", cancellationCategories: "/orders/cancellation-categories", returnCategories: "/orders/return-categories", + detailUser: "/orders/detail-user/", + detailSeller: "/orders/detail-seller/", }, }; diff --git a/src/pages/blogs/Create.tsx b/src/pages/blogs/Create.tsx new file mode 100644 index 0000000..51d5616 --- /dev/null +++ b/src/pages/blogs/Create.tsx @@ -0,0 +1,105 @@ +import { type FC, useState } from 'react' +import { useFormik } from 'formik' +import { type BlogFormType, type BlogCategory } from './types/Types' +import * as Yup from 'yup' +import { useCreateBlog, useGetBlogCategories } from './hooks/useBlogsData' +import { useNavigate } from 'react-router-dom' +import Button from '../../components/Button' +import { TickCircle } from 'iconsax-react' +import { useUploadSingle } from '../uploader/hooks/useUploaderData' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { BlogFormFields, FileUploadSection } from './components' +import { Pages } from '@/config/Pages' + +const CreateBlog: FC = () => { + const navigate = useNavigate() + const { data: categoriesData } = useGetBlogCategories() + const uploadSingle = useUploadSingle() + const createBlogMutation = useCreateBlog() + + const [imageFile, setImageFile] = useState([]) + + const formik = useFormik({ + initialValues: { + title_fa: '', + slug: '', + category: '', + description: '', + content: '', + tags: [], + imageUrl: '', + }, + validationSchema: Yup.object({ + title_fa: Yup.string().required('عنوان فارسی بلاگ الزامی است'), + slug: Yup.string().required('اسلاگ بلاگ الزامی است'), + category: Yup.string().required('دسته بندی بلاگ الزامی است'), + description: Yup.string().required('توضیحات بلاگ الزامی است'), + content: Yup.string().required('محتوای بلاگ الزامی است'), + }), + onSubmit: async (values) => { + let imageUrl = values.imageUrl + + if (imageFile.length > 0) { + const imageResponse = await uploadSingle.mutateAsync(imageFile[0]) + imageUrl = imageResponse.results?.url?.url || '' + } + + const submitData = { + ...values, + imageUrl + } + + createBlogMutation.mutate(submitData, { + onSuccess: () => { + toast.success('بلاگ با موفقیت ایجاد شد') + navigate(Pages.blog.list) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + }, + }) + + const categoryOptions = categoriesData?.results?.categories?.map((category: BlogCategory) => ({ + value: category._id, + label: category.title_fa + })) || [] + + return ( +
+
+
+

ساخت بلاگ جدید

+
+
+ +
+
+ +
+ {/* اطلاعات اصلی */} +
+

اطلاعات اصلی

+ +
+ + {/* فایل‌ها */} + +
+
+ ) +} + +export default CreateBlog \ No newline at end of file diff --git a/src/pages/blogs/List.tsx b/src/pages/blogs/List.tsx new file mode 100644 index 0000000..cd3db6e --- /dev/null +++ b/src/pages/blogs/List.tsx @@ -0,0 +1,136 @@ +import { type FC, useState } from 'react' +import { useDeleteBlog, useGetBlogs } from './hooks/useBlogsData' +import { useQueryClient } from '@tanstack/react-query' +import { type Blog } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import TrashWithConfrim from '../../components/TrashWithConfrim' +import { Edit, Eye } from 'iconsax-react' +import { Link, useNavigate } from 'react-router-dom' +import { Pages } from '@/config/Pages' +import Button from '@/components/Button' + +const BlogsList: FC = () => { + + const navigate = useNavigate() + const queryClient = useQueryClient() + const [currentPage, setCurrentPage] = useState(1) + const deleteBlog = useDeleteBlog() + const { data: blogsData, isLoading, error } = useGetBlogs(currentPage) + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const blogs = blogsData?.blogs || [] + const pager = blogsData?.pager + + return ( +
+
+
+
+ + + + + + + {blogs.length === 0 ? ( + + + + ) : ( + blogs.map((blog: Blog) => ( + + + + + + + )) + )} + +
+ + + + + + +
+ هیچ بلاگی یافت نشد +
+
+ {blog.title_fa} +
+
+
+
+ {blog.title_fa} +
+
+ {blog.slug} +
+
+
+ + +
+ + {blog.view.toLocaleString('fa-IR')} +
+
+ +
+ + + + + { + deleteBlog.mutate(blog._id, { + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['blogs'] }) + } + }) + }} + /> +
+
+
+ + { + setCurrentPage(page) + }} + /> +
+ ) +} + +export default BlogsList \ No newline at end of file diff --git a/src/pages/blogs/Update.tsx b/src/pages/blogs/Update.tsx new file mode 100644 index 0000000..e2f3386 --- /dev/null +++ b/src/pages/blogs/Update.tsx @@ -0,0 +1,134 @@ +import { type FC, useState, useEffect } from 'react' +import { useParams } from 'react-router-dom' +import { useFormik } from 'formik' +import { type BlogFormType } from './types/Types' +import * as Yup from 'yup' +import { useGetBlogDetail, useGetBlogCategories, useUpdateBlog } from './hooks/useBlogsData' +import { useNavigate } from 'react-router-dom' +import Button from '../../components/Button' +import { TickCircle } from 'iconsax-react' +import { useUploadSingle } from '../uploader/hooks/useUploaderData' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' +import { BlogFormFields, FileUploadSection } from './components' +import { Pages } from '@/config/Pages' + +const UpdateBlog: FC = () => { + const navigate = useNavigate() + const { id } = useParams<{ id: string }>() + const { data: blogDetail, isLoading: blogDetailLoading, error: blogDetailError } = useGetBlogDetail(id!) + const { data: categoriesData } = useGetBlogCategories() + const uploadSingle = useUploadSingle() + const updateBlogMutation = useUpdateBlog() + + const [imageFile, setImageFile] = useState([]) + const [blogId, setBlogId] = useState('') + + const formik = useFormik({ + initialValues: { + title_fa: '', + slug: '', + category: '', + description: '', + content: '', + tags: [], + imageUrl: '', + }, + validationSchema: Yup.object({ + title_fa: Yup.string().required('عنوان فارسی بلاگ الزامی است'), + slug: Yup.string().required('اسلاگ بلاگ الزامی است'), + category: Yup.string().required('دسته بندی بلاگ الزامی است'), + description: Yup.string().required('توضیحات بلاگ الزامی است'), + content: Yup.string().required('محتوای بلاگ الزامی است'), + }), + onSubmit: async (values) => { + let imageUrl = values.imageUrl + + if (imageFile.length > 0) { + const imageResponse = await uploadSingle.mutateAsync(imageFile[0]) + imageUrl = imageResponse.results?.url?.url || '' + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { slug: _slug, ...valuesWithoutSlug } = values + const submitData = { + ...valuesWithoutSlug, + imageUrl + } + + updateBlogMutation.mutate({ _id: blogId, ...submitData }, { + onSuccess: () => { + toast.success('بلاگ با موفقیت ویرایش شد') + navigate(Pages.blog.list) + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + }, + }) + + useEffect(() => { + if (blogDetail) { + setBlogId(blogDetail._id) + formik.setValues({ + title_fa: blogDetail.title_fa, + slug: blogDetail.slug, + category: blogDetail.category._id, + description: blogDetail.description, + content: blogDetail.content, + tags: blogDetail.tags, + imageUrl: blogDetail.imageUrl, + }) + } + }, [blogDetail, formik]) + + const categoryOptions = categoriesData?.results?.categories?.map((category) => ({ + value: category._id, + label: category.title_fa + })) || [] + + if (blogDetailLoading) { + return
در حال بارگذاری...
+ } + + if (blogDetailError) { + return
خطا در بارگذاری داده‌ها
+ } + + return ( +
+
+
+

ویرایش بلاگ

+
+
+ +
+
+ +
+ {/* اطلاعات اصلی */} +
+

اطلاعات اصلی

+ +
+ + {/* فایل‌ها */} + +
+
+ ) +} + +export default UpdateBlog \ No newline at end of file diff --git a/src/pages/blogs/components/BlogFormFields.tsx b/src/pages/blogs/components/BlogFormFields.tsx new file mode 100644 index 0000000..55bf199 --- /dev/null +++ b/src/pages/blogs/components/BlogFormFields.tsx @@ -0,0 +1,66 @@ +import { type FC } from 'react' +import { type FormikProps } from 'formik' +import Input from '../../../components/Input' +import Select from '../../../components/Select' +import Textarea from '../../../components/Textarea' +import { type BlogFormType } from '../types/Types' +import { TagManager } from './TagManager' + +interface BlogFormFieldsProps { + formik: FormikProps + categoryOptions: { value: string; label: string }[] +} + +export const BlogFormFields: FC = ({ formik, categoryOptions }) => { + const getFieldError = (field: keyof BlogFormType) => { + const error = formik.touched[field] && formik.errors[field] + return error ? (Array.isArray(error) ? error[0] : error) : '' + } + + return ( +
+
+ + +
+ +