+26
-10
@@ -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<number>(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 (
|
||||
<div className="mt-5">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-lg font-light">مدیران</h1>
|
||||
<Link to={Paths.admin.create}>
|
||||
<Button className="w-fit px-6">
|
||||
<div className="flex gap-1.5">
|
||||
<AddSquare size={18} color="black" />
|
||||
<div className="text-[13px] font-light">ادمین جدید</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
<div className="flex gap-2">
|
||||
{canManageRoles && (
|
||||
<Link to={Paths.role.list}>
|
||||
<Button className="w-fit px-6 bg-[#F5F7FC]">
|
||||
<div className="flex gap-1.5">
|
||||
<ShieldSecurity size={18} color="black" />
|
||||
<div className="text-[13px] font-light text-black">نقشها</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
<Link to={Paths.admin.create}>
|
||||
<Button className="w-fit px-6">
|
||||
<div className="flex gap-1.5">
|
||||
<AddSquare size={18} color="black" />
|
||||
<div className="text-[13px] font-light">ادمین جدید</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as api from "../service/AdminService";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { CreateAdminType } from "../types/Types";
|
||||
import type { CreateAdminType, UpdateAdminMeType } from "../types/Types";
|
||||
|
||||
export const useGetAdmins = (page: number = 1, limit: number = 10) => {
|
||||
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"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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<AdminMeResponseType>("/admin/admins/me");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateAdminMe = async (params: UpdateAdminMeType) => {
|
||||
const { data } = await axios.patch<AdminMeResponseType>("/admin/admins/me", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -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<AdminMeDataType>;
|
||||
|
||||
@@ -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<File>();
|
||||
|
||||
const admin = adminData?.data;
|
||||
|
||||
const handleSave = (values: UpdateAdminMeType) => {
|
||||
update(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('پروفایل با موفقیت ویرایش شد');
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const formik = useFormik<UpdateAdminMeType>({
|
||||
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 (
|
||||
<div className='mt-5 flex h-64 items-center justify-center'>
|
||||
<div className='text-lg'>در حال بارگذاری...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<h1 className='text-lg font-light'>پروفایل</h1>
|
||||
<Button
|
||||
className='w-fit px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={isUpdating || isUploading}
|
||||
>
|
||||
<div className='flex gap-1.5'>
|
||||
<Edit size={18} color='black' />
|
||||
<div className='text-[13px] font-light'>ذخیره تغییرات</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex-1 rounded-3xl bg-white p-6'>
|
||||
{formik.values.avatarUrl && !file && (
|
||||
<div className='mb-4'>
|
||||
<PresignedImage
|
||||
src={formik.values.avatarUrl}
|
||||
className='h-20 w-20 rounded-full object-cover'
|
||||
alt='آواتار'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<UploadBox
|
||||
label='آواتار (اختیاری)'
|
||||
onChange={(files) => setFile(files[0])}
|
||||
/>
|
||||
|
||||
<div className='rowTwoInput mt-6'>
|
||||
<Input
|
||||
label='نام'
|
||||
placeholder='نام را وارد کنید'
|
||||
{...formik.getFieldProps('firstName')}
|
||||
error_text={
|
||||
formik.touched.firstName && formik.errors.firstName
|
||||
? formik.errors.firstName
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
|
||||
<Input
|
||||
label='نام خانوادگی'
|
||||
placeholder='نام خانوادگی را وارد کنید'
|
||||
{...formik.getFieldProps('lastName')}
|
||||
error_text={
|
||||
formik.touched.lastName && formik.errors.lastName
|
||||
? formik.errors.lastName
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Input
|
||||
label='شماره موبایل'
|
||||
placeholder='شماره موبایل را وارد کنید'
|
||||
{...formik.getFieldProps('phone')}
|
||||
error_text={
|
||||
formik.touched.phone && formik.errors.phone
|
||||
? formik.errors.phone
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Profile;
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user