list and create admin
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
import { useFormik } from 'formik'
|
||||
import { type FC } from 'react'
|
||||
import { useGetPermissions, useCreateAdmin } from './hooks/useAdminData'
|
||||
import type { CreateAdminType } from './types/Types'
|
||||
import * as Yup from 'yup'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Input from '../../components/Input'
|
||||
import MultiSelectSearchable from '../../components/MultiSelectSearchable'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/helpers/utils'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const Create: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { data: permissionsData } = useGetPermissions()
|
||||
const createAdminMutation = useCreateAdmin()
|
||||
|
||||
const formik = useFormik<CreateAdminType>({
|
||||
initialValues: {
|
||||
fullName: "",
|
||||
userName: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
permissions: [],
|
||||
password: "",
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
fullName: Yup.string().required("نام کامل الزامی است"),
|
||||
userName: Yup.string().required("نام کاربری الزامی است"),
|
||||
email: Yup.string().email("ایمیل معتبر نیست").required("ایمیل الزامی است"),
|
||||
phoneNumber: Yup.string().required("شماره تلفن الزامی است"),
|
||||
permissions: Yup.array().min(1, "دسترسی های الزامی است").required("دسترسی های الزامی است"),
|
||||
password: Yup.string().min(6, "رمز عبور باید حداقل ۶ کاراکتر باشد").required("رمز عبور الزامی است"),
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
createAdminMutation.mutate(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('ادمین با موفقیت ایجاد شد')
|
||||
navigate(Pages.admin.list)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const permissionsOptions = permissionsData?.results?.permissions?.map((permission) => ({
|
||||
value: permission._id,
|
||||
label: permission.name
|
||||
})) || []
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h1>ساخت ادمین جدید</h1>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className='px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={createAdminMutation.isPending}
|
||||
disabled={!formik.isValid || createAdminMutation.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={16} color='#fff' />
|
||||
<div>ثبت ادمین</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||
{/* اطلاعات اصلی */}
|
||||
<div className='flex-1 text-sm bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||
<h2 className='text-lg font-medium mb-6'>اطلاعات اصلی</h2>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||
<Input
|
||||
label='نام کامل'
|
||||
placeholder='مثال: علی احمدی'
|
||||
{...formik.getFieldProps('fullName')}
|
||||
error_text={formik.touched.fullName && formik.errors.fullName ? formik.errors.fullName : ''}
|
||||
/>
|
||||
<Input
|
||||
label='نام کاربری'
|
||||
placeholder='مثال: ali_ahmadi'
|
||||
{...formik.getFieldProps('userName')}
|
||||
error_text={formik.touched.userName && formik.errors.userName ? formik.errors.userName : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||
<Input
|
||||
label='ایمیل'
|
||||
placeholder='مثال: ali@example.com'
|
||||
type='email'
|
||||
{...formik.getFieldProps('email')}
|
||||
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||
/>
|
||||
<Input
|
||||
label='شماره تلفن'
|
||||
placeholder='مثال: ۰۹۱۲۳۴۵۶۷۸۹'
|
||||
{...formik.getFieldProps('phoneNumber')}
|
||||
error_text={formik.touched.phoneNumber && formik.errors.phoneNumber ? formik.errors.phoneNumber : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label='رمز عبور'
|
||||
placeholder='رمز عبور را وارد کنید'
|
||||
type='password'
|
||||
{...formik.getFieldProps('password')}
|
||||
error_text={formik.touched.password && formik.errors.password ? formik.errors.password : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* دسترسیها */}
|
||||
<div className='w-80 space-y-6'>
|
||||
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
||||
<h2 className='text-lg font-medium mb-6'>دسترسیها</h2>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<MultiSelectSearchable
|
||||
label='دسترسیهای ادمین'
|
||||
placeholder='انتخاب دسترسیها'
|
||||
options={permissionsOptions}
|
||||
value={formik.values.permissions}
|
||||
onChange={(value) => formik.setFieldValue('permissions', value)}
|
||||
error_text={formik.touched.permissions && formik.errors.permissions ? String(formik.errors.permissions) : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Create
|
||||
@@ -0,0 +1,136 @@
|
||||
import { type FC, useState } from 'react'
|
||||
import { useDeleteAdmin, useGetAdmins } from './hooks/useAdminData'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { type Admin } from './types/Types'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import Error from '../../components/Error'
|
||||
import PaginationUi from '../../components/PaginationUi'
|
||||
import Td from '../../components/Td'
|
||||
import { Calendar, Call, Card, User as UserIcon } from 'iconsax-react'
|
||||
import Button from '@/components/Button'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Pages } from '@/config/Pages'
|
||||
import TrashWithConfrim from '../../components/TrashWithConfrim'
|
||||
|
||||
const List: FC = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { data: adminsData, isLoading, error } = useGetAdmins(currentPage);
|
||||
const deleteAdminMutation = useDeleteAdmin();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<PageLoading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<Error errorText="خطا در بارگذاری ادمینها" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const admins = adminsData?.results?.admins || [];
|
||||
const pager = adminsData?.results?.pager;
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
if (!dateString) return '-';
|
||||
return new Date(dateString).toLocaleDateString('fa-IR');
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-end mt-5'>
|
||||
<Button
|
||||
label='افزودن ادمین'
|
||||
onClick={() => navigate(`${Pages.admin.create}`)}
|
||||
className='w-fit'
|
||||
/>
|
||||
</div>
|
||||
<div className='relative overflow-x-auto rounded-3xl mt-5 w-full'>
|
||||
<table className='w-full text-sm'>
|
||||
<thead className='thead'>
|
||||
<tr>
|
||||
<Td text={'نام و نام خانوادگی'} />
|
||||
<Td text={'نام کاربری'} />
|
||||
<Td text={'ایمیل'} />
|
||||
<Td text={'شماره تلفن'} />
|
||||
<Td text={'نقش'} />
|
||||
<Td text={'تاریخ ایجاد'} />
|
||||
<Td text={'عملیات'} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{admins.length === 0 ? (
|
||||
<tr className='tr'>
|
||||
<td colSpan={7} className="text-center py-8 text-gray-500">
|
||||
هیچ ادمینی یافت نشد
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
admins.map((admin: Admin) => (
|
||||
<tr key={admin._id} className='tr'>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserIcon size={16} color="#8C90A3" />
|
||||
<span>{admin.fullName || '-'}</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={admin.userName} />
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Card size={16} color="#8C90A3" />
|
||||
<span>{admin.email}</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Call size={16} color="#8C90A3" />
|
||||
<span>{admin.phoneNumber}</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text={admin.role?.name || '-'} />
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar size={16} color="#8C90A3" />
|
||||
<span>{formatDate(admin.createdAt)}</span>
|
||||
</div>
|
||||
</Td>
|
||||
<Td text="">
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
<TrashWithConfrim
|
||||
onDelete={() => {
|
||||
deleteAdminMutation.mutate(admin._id, {
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admins'] })
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Td>
|
||||
</tr>
|
||||
))
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<PaginationUi
|
||||
pager={pager}
|
||||
onPageChange={(page) => {
|
||||
setCurrentPage(page);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default List
|
||||
@@ -0,0 +1,159 @@
|
||||
import { useFormik } from 'formik'
|
||||
import { type FC, useEffect } from 'react'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import { useGetPermissions, useGetAdminDetail, useUpdateAdmin } from './hooks/useAdminData'
|
||||
import type { UpdateAdminType } from './types/Types'
|
||||
import * as Yup from 'yup'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Input from '../../components/Input'
|
||||
import MultiSelectSearchable from '../../components/MultiSelectSearchable'
|
||||
import Button from '../../components/Button'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { extractErrorMessage } from '@/helpers/utils'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const Update: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const { data: permissionsData } = useGetPermissions()
|
||||
const { data: adminDetail, isLoading: adminDetailLoading, error: adminDetailError } = useGetAdminDetail(id!)
|
||||
const updateAdminMutation = useUpdateAdmin()
|
||||
|
||||
const formik = useFormik<UpdateAdminType>({
|
||||
initialValues: {
|
||||
fullName: "",
|
||||
userName: "",
|
||||
email: "",
|
||||
phoneNumber: "",
|
||||
permissions: [],
|
||||
},
|
||||
validationSchema: Yup.object({
|
||||
fullName: Yup.string().required("نام کامل الزامی است"),
|
||||
userName: Yup.string().required("نام کاربری الزامی است"),
|
||||
email: Yup.string().email("ایمیل معتبر نیست").required("ایمیل الزامی است"),
|
||||
phoneNumber: Yup.string().required("شماره تلفن الزامی است"),
|
||||
permissions: Yup.array().min(1, "دسترسی های الزامی است").required("دسترسی های الزامی است"),
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
updateAdminMutation.mutate({ id: id!, ...values }, {
|
||||
onSuccess: () => {
|
||||
toast.success('ادمین با موفقیت ویرایش شد')
|
||||
navigate(Pages.admin.list)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (adminDetail) {
|
||||
formik.setValues({
|
||||
fullName: adminDetail.results.admin.fullName,
|
||||
userName: adminDetail.results.admin.userName,
|
||||
email: adminDetail.results.admin.email,
|
||||
phoneNumber: adminDetail.results.admin.phoneNumber,
|
||||
permissions: adminDetail.results.admin.permissions,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [adminDetail])
|
||||
|
||||
const permissionsOptions = permissionsData?.results?.permissions?.map((permission) => ({
|
||||
value: permission._id,
|
||||
label: permission.name
|
||||
})) || []
|
||||
|
||||
if (adminDetailLoading) {
|
||||
return <div>در حال بارگذاری...</div>
|
||||
}
|
||||
|
||||
if (adminDetailError) {
|
||||
return <div>خطا در بارگذاری دادهها</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='flex items-center gap-3'>
|
||||
<h1>ویرایش ادمین</h1>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
className='px-6'
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={updateAdminMutation.isPending}
|
||||
disabled={!formik.isValid || updateAdminMutation.isPending}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle size={16} color='#fff' />
|
||||
<div>ویرایش ادمین</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-6 xl:mt-8 mt-6'>
|
||||
{/* اطلاعات اصلی */}
|
||||
<div className='flex-1 text-sm bg-white py-8 xl:px-10 px-6 rounded-3xl'>
|
||||
<h2 className='text-lg font-medium mb-6'>اطلاعات اصلی</h2>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||
<Input
|
||||
label='نام کامل'
|
||||
placeholder='مثال: علی احمدی'
|
||||
{...formik.getFieldProps('fullName')}
|
||||
error_text={formik.touched.fullName && formik.errors.fullName ? formik.errors.fullName : ''}
|
||||
/>
|
||||
<Input
|
||||
label='نام کاربری'
|
||||
placeholder='مثال: ali_ahmadi'
|
||||
{...formik.getFieldProps('userName')}
|
||||
error_text={formik.touched.userName && formik.errors.userName ? formik.errors.userName : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
|
||||
<Input
|
||||
label='ایمیل'
|
||||
placeholder='مثال: ali@example.com'
|
||||
type='email'
|
||||
{...formik.getFieldProps('email')}
|
||||
error_text={formik.touched.email && formik.errors.email ? formik.errors.email : ''}
|
||||
/>
|
||||
<Input
|
||||
label='شماره تلفن'
|
||||
placeholder='مثال: ۰۹۱۲۳۴۵۶۷۸۹'
|
||||
{...formik.getFieldProps('phoneNumber')}
|
||||
error_text={formik.touched.phoneNumber && formik.errors.phoneNumber ? formik.errors.phoneNumber : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* دسترسیها */}
|
||||
<div className='w-80 space-y-6'>
|
||||
<div className='text-sm bg-white py-8 px-6 rounded-3xl'>
|
||||
<h2 className='text-lg font-medium mb-6'>دسترسیها</h2>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<MultiSelectSearchable
|
||||
label='دسترسیهای ادمین'
|
||||
placeholder='انتخاب دسترسیها'
|
||||
options={permissionsOptions}
|
||||
value={formik.values.permissions}
|
||||
onChange={(value) => formik.setFieldValue('permissions', value)}
|
||||
error_text={formik.touched.permissions && formik.errors.permissions ? String(formik.errors.permissions) : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Update
|
||||
@@ -0,0 +1,42 @@
|
||||
import * as api from "../service/AdminService";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
export const useGetAdmins = (page: number = 1, limit: number = 10) => {
|
||||
return useQuery({
|
||||
queryKey: ["admins", page, limit],
|
||||
queryFn: () => api.getAdmins(page, limit),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetPermissions = () => {
|
||||
return useQuery({
|
||||
queryKey: ["permissions"],
|
||||
queryFn: () => api.getPermissions(),
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateAdmin = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.createAdmin,
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteAdmin = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.deleteAdmin,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetAdminDetail = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["admin", id],
|
||||
queryFn: () => api.getAdminDetail(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAdmin = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.updateAdmin,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import axios from "../../../config/axios";
|
||||
import type {
|
||||
GetPermissionsResponse,
|
||||
CreateAdminType,
|
||||
UpdateAdminType,
|
||||
GetAdminsResponse,
|
||||
Admin,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getAdmins = async (
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
): Promise<GetAdminsResponse> => {
|
||||
const { data } = await axios.get(`/admin/admins`, {
|
||||
params: { page, limit },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getPermissions = async (): Promise<GetPermissionsResponse> => {
|
||||
const { data } = await axios.get(`/admin/permissions`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createAdmin = async (admin: CreateAdminType) => {
|
||||
const { data } = await axios.post(`/admin/create`, admin);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteAdmin = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/${id}/delete`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getAdminDetail = async (
|
||||
id: string
|
||||
): Promise<{ status: number; success: boolean; results: { admin: Admin } }> => {
|
||||
const { data } = await axios.get(`/admin/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateAdmin = async ({
|
||||
id,
|
||||
...admin
|
||||
}: { id: string } & UpdateAdminType) => {
|
||||
const { data } = await axios.put(`/admin/${id}/update`, admin);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
export interface Permission {
|
||||
_id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface GetPermissionsResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
permissions: Permission[];
|
||||
};
|
||||
}
|
||||
|
||||
export type CreateAdminType = {
|
||||
fullName: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
permissions: string[];
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type UpdateAdminType = {
|
||||
fullName: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
permissions: string[];
|
||||
};
|
||||
|
||||
export interface AdminRole {
|
||||
_id: string;
|
||||
name: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreatedBy {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface Admin {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
phoneNumber: string;
|
||||
role: AdminRole;
|
||||
createdBy: CreatedBy;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
export interface Pager {
|
||||
page: number;
|
||||
limit: number;
|
||||
totalItems: number;
|
||||
totalPages: number;
|
||||
prevPage: boolean;
|
||||
nextPage: string | null;
|
||||
}
|
||||
|
||||
export interface GetAdminsResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
admins: Admin[];
|
||||
pager: Pager;
|
||||
};
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import CouponList from '@/pages/Coupon/List'
|
||||
import CreateCoupon from '@/pages/Coupon/Create'
|
||||
import UpdateCoupon from '@/pages/Coupon/Update'
|
||||
import UsersList from '@/pages/user/List'
|
||||
import AdminList from '@/pages/admin/List'
|
||||
import AdminCreate from '@/pages/admin/Create'
|
||||
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
@@ -86,6 +88,10 @@ const MainRouter: FC = () => {
|
||||
<Route path={`${Pages.coupon.detail}:id`} element={<UpdateCoupon />} />
|
||||
|
||||
<Route path={Pages.buyUsers.list} element={<UsersList />} />
|
||||
|
||||
<Route path={Pages.admin.list} element={<AdminList />} />
|
||||
<Route path={Pages.admin.create} element={<AdminCreate />} />
|
||||
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user