From 71ee8f4575f7672572cb737d62e7bdc2ce7ed5f7 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 21 Jul 2026 16:04:57 +0330 Subject: [PATCH] profile --- src/config/Paths.tsx | 1 + src/pages/admin/List.tsx | 36 ++++-- src/pages/admin/hooks/useAdminData.ts | 13 +- src/pages/admin/service/AdminService.ts | 6 + src/pages/admin/types/Types.ts | 7 + src/pages/profile/Profile.tsx | 148 ++++++++++++++++++++++ src/pages/profile/hooks/useProfileData.ts | 21 --- src/router/MainRouter.tsx | 2 + src/shared/Header.tsx | 101 +++------------ src/shared/Sidebar.tsx | 11 +- 10 files changed, 222 insertions(+), 124 deletions(-) create mode 100644 src/pages/profile/Profile.tsx delete mode 100644 src/pages/profile/hooks/useProfileData.ts diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx index cf3f56c..f24df0f 100644 --- a/src/config/Paths.tsx +++ b/src/config/Paths.tsx @@ -56,6 +56,7 @@ export const Paths = { print: '/service/print', }, home: '/home', + profile: '/profile', myOrders: '/my-orders', perfomaInvoice: { list: '/invoice/list', diff --git a/src/pages/admin/List.tsx b/src/pages/admin/List.tsx index fcbe8bb..4f6e521 100644 --- a/src/pages/admin/List.tsx +++ b/src/pages/admin/List.tsx @@ -1,31 +1,47 @@ import { useState, type FC } from "react"; -import { useGetAdmins, useDeleteAdmin } from "./hooks/useAdminData"; +import { useGetAdmins, useDeleteAdmin, useGetAdminMe } from "./hooks/useAdminData"; import Table from "@/components/Table"; import PresignedImage from "@/components/PresignedImage"; import moment from "moment-jalaali"; import { Link } from "react-router-dom"; import { Paths } from "@/config/Paths"; import Button from "@/components/Button"; -import { AddSquare, Eye } from "iconsax-react"; +import { AddSquare, Eye, ShieldSecurity } from "iconsax-react"; import TrashWithConfrim from "@/components/TrashWithConfrim"; const AdminList: FC = () => { const [page, setPage] = useState(1); const { data } = useGetAdmins(page); const deleteAdminMutation = useDeleteAdmin(); + const { data: adminMe } = useGetAdminMe(); + const canManageRoles = adminMe?.data?.role?.permissions?.some( + (p) => p.name === "manage_roles", + ); return (

مدیران

- - - +
+ {canManageRoles && ( + + + + )} + + + +
{ return useQuery({ @@ -59,3 +59,14 @@ export const useGetAdminMe = () => { queryFn: api.getAdminMe, }); }; + +export const useUpdateAdminMe = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (params: UpdateAdminMeType) => api.updateAdminMe(params), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["adminMe"] }); + }, + }); +}; diff --git a/src/pages/admin/service/AdminService.ts b/src/pages/admin/service/AdminService.ts index 28e7df0..5bfaa5a 100644 --- a/src/pages/admin/service/AdminService.ts +++ b/src/pages/admin/service/AdminService.ts @@ -5,6 +5,7 @@ import type { RolesResponseType, AdminDetailResponseType, AdminMeResponseType, + UpdateAdminMeType, } from "../types/Types"; export const getAdmins = async (page: number, limit: number = 10) => { @@ -52,3 +53,8 @@ export const getAdminMe = async () => { const { data } = await axios.get("/admin/admins/me"); return data; }; + +export const updateAdminMe = async (params: UpdateAdminMeType) => { + const { data } = await axios.patch("/admin/admins/me", params); + return data; +}; diff --git a/src/pages/admin/types/Types.ts b/src/pages/admin/types/Types.ts index 70b1bc2..66b0882 100644 --- a/src/pages/admin/types/Types.ts +++ b/src/pages/admin/types/Types.ts @@ -65,4 +65,11 @@ export interface AdminMeDataType { avatarUrl?: string | null; role: RoleItemType; } +export type UpdateAdminMeType = { + phone: string; + firstName: string; + lastName: string; + avatarUrl?: string; +}; + export type AdminMeResponseType = BaseResponse; diff --git a/src/pages/profile/Profile.tsx b/src/pages/profile/Profile.tsx new file mode 100644 index 0000000..d449d0b --- /dev/null +++ b/src/pages/profile/Profile.tsx @@ -0,0 +1,148 @@ +import { useFormik } from 'formik'; +import { type FC, useState } from 'react'; +import * as Yup from 'yup'; +import Button from '@/components/Button'; +import { Edit } from 'iconsax-react'; +import Input from '@/components/Input'; +import UploadBox from '@/components/UploadBox'; +import PresignedImage from '@/components/PresignedImage'; +import { useGetAdminMe, useUpdateAdminMe } from '@/pages/admin/hooks/useAdminData'; +import { useSingleUpload } from '@/pages/uploader/hooks/useUploader'; +import { toast } from 'react-toastify'; +import { extractErrorMessage } from '@/config/func'; +import type { UpdateAdminMeType } from '@/pages/admin/types/Types'; + +const Profile: FC = () => { + const { data: adminData, isLoading } = useGetAdminMe(); + const { mutate: update, isPending: isUpdating } = useUpdateAdminMe(); + const { mutate: upload, isPending: isUploading } = useSingleUpload(); + const [file, setFile] = useState(); + + const admin = adminData?.data; + + const handleSave = (values: UpdateAdminMeType) => { + update(values, { + onSuccess: () => { + toast.success('پروفایل با موفقیت ویرایش شد'); + }, + onError: (error) => { + toast.error(extractErrorMessage(error)); + }, + }); + }; + + const formik = useFormik({ + enableReinitialize: true, + initialValues: { + firstName: admin?.firstName ?? '', + lastName: admin?.lastName ?? '', + phone: admin?.phone ? `0${admin.phone}` : '', + avatarUrl: admin?.avatarUrl ?? undefined, + }, + validationSchema: Yup.object({ + firstName: Yup.string() + .required('نام اجباری می باشد') + .min(2, 'نام باید حداقل 2 کاراکتر باشد'), + lastName: Yup.string() + .required('نام خانوادگی اجباری می باشد') + .min(2, 'نام خانوادگی باید حداقل 2 کاراکتر باشد'), + phone: Yup.string() + .required('شماره موبایل اجباری می باشد') + .matches(/^09[0-9]{9}$/, 'شماره موبایل معتبر نمی باشد'), + }), + onSubmit: (values) => { + if (file) { + upload(file, { + onSuccess: (data) => { + handleSave({ ...values, avatarUrl: data?.data?.key }); + }, + onError: (error) => toast.error(extractErrorMessage(error)), + }); + } else { + handleSave(values); + } + }, + }); + + if (isLoading) { + return ( +
+
در حال بارگذاری...
+
+ ); + } + + return ( +
+
+

پروفایل

+ +
+ +
+ {formik.values.avatarUrl && !file && ( +
+ +
+ )} + + setFile(files[0])} + /> + +
+ + + +
+ +
+ +
+
+
+ ); +}; + +export default Profile; diff --git a/src/pages/profile/hooks/useProfileData.ts b/src/pages/profile/hooks/useProfileData.ts deleted file mode 100644 index 3c724b4..0000000 --- a/src/pages/profile/hooks/useProfileData.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useQuery } from "@tanstack/react-query"; -import axios from "@/config/axios"; - -export interface ProfileData { - user: { - firstName: string; - lastName: string; - email: string; - profilePic?: string; - }; -} - -export const useGetProfile = () => { - return useQuery({ - queryKey: ["profile"], - queryFn: async (): Promise<{ data: ProfileData }> => { - const response = await axios.get("/profile"); - return response.data; - }, - }); -}; diff --git a/src/router/MainRouter.tsx b/src/router/MainRouter.tsx index d99f85f..50ee06c 100644 --- a/src/router/MainRouter.tsx +++ b/src/router/MainRouter.tsx @@ -47,6 +47,7 @@ import UpdateUser from "@/pages/user/Update"; import AdminList from "@/pages/admin/List"; import CreateAdmin from "@/pages/admin/Create"; import EditAdmin from "@/pages/admin/Update"; +import Profile from "@/pages/profile/Profile"; import RoleList from "@/pages/role/List"; import CreateRole from "@/pages/role/Create"; import UpdateRole from "@/pages/role/Update"; @@ -75,6 +76,7 @@ const MainRouter: FC = () => { } /> } /> + } /> } /> } /> } /> diff --git a/src/shared/Header.tsx b/src/shared/Header.tsx index 9ff806b..a2244ce 100644 --- a/src/shared/Header.tsx +++ b/src/shared/Header.tsx @@ -1,21 +1,16 @@ -import { type FC, useEffect, useState } from 'react' +import { type FC } from 'react' import Input from '@/components/Input' import UserAvatar from '@/components/UserAvatar' -import { ArrowDown2, CloseCircle, HambergerMenu, Logout } from 'iconsax-react' +import { HambergerMenu } from 'iconsax-react' import { Link } from 'react-router-dom' -import { useLocation } from 'react-router-dom' import Notifications from '@/pages/notification/Notification' import { useSharedStore } from './store/useSharedStore' -import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react' -import SidebarItem from './SidebarItem' import { t } from '@/locale' import { useGetAdminMe } from '@/pages/admin/hooks/useAdminData' import { Paths } from '@/config/Paths' const Header: FC = () => { - const location = useLocation(); - const [popoverKey, setPopoverKey] = useState(0); const { setOpenSidebar, openSidebar } = useSharedStore() const { data } = useGetAdminMe() const admin = data?.data @@ -23,10 +18,6 @@ const Header: FC = () => { const displayName = [admin?.firstName, admin?.lastName].filter(Boolean).join(' ').trim() const roleTitle = admin?.role?.title || admin?.role?.name || '' - useEffect(() => { - setPopoverKey((prevKey) => prevKey + 1); - }, [location.pathname]); - return (
@@ -39,82 +30,28 @@ const Header: FC = () => {
setOpenSidebar(!openSidebar)} className='xl:hidden block'>
- {/* */}
- {/* - - - - - - */} {admin && ( - - -
- -
-
-
- {displayName || String(admin.phone ?? '')} -
- {roleTitle && ( -
- {roleTitle} -
- )} -
- -
+ + +
+
+ {displayName || String(admin.phone ?? '')}
- - - -
- setPopoverKey((prevKey) => prevKey + 1)} size={20} color='black' /> -
- - -
- {displayName || String(admin.phone ?? '')} + {roleTitle && ( +
+ {roleTitle}
- {roleTitle && ( -
- {roleTitle} -
- )} -
- {String(admin.phone ?? '')} -
- - - -
- } - title={t('sidebar.logout')} - isActive - link={Paths.auth.login} - isWithoutLine - isLogout - activeName='' - /> -
- - + )} +
+ )}
diff --git a/src/shared/Sidebar.tsx b/src/shared/Sidebar.tsx index 7596a98..74eb713 100644 --- a/src/shared/Sidebar.tsx +++ b/src/shared/Sidebar.tsx @@ -13,7 +13,6 @@ import { Printer, Receipt21, MoneyRecive, - ShieldSecurity, Teacher, User, } from 'iconsax-react'; @@ -128,20 +127,12 @@ const SideBar: FC = () => { } - title={'ادمین'} + title={'مدیران'} isActive={isActive('admin')} link={Paths.admin.list} activeName='manage_admins' /> - } - title={'نقش‌ها'} - isActive={isActive('role')} - link={Paths.role.list} - activeName='manage_roles' - /> - } title={'محصولات'}