create coupon
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { type FC, useState, useMemo } from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
import { TickCircle } from 'iconsax-react'
|
||||
import { toast } from 'react-toastify'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import Button from '@/components/Button'
|
||||
import type { CreateCouponType } from './types/Types'
|
||||
import { useCreateCoupon } from './hooks/useCouponData'
|
||||
import { useGetCategories } from '@/pages/food/hooks/useFoodData'
|
||||
import { useGetFoods } from '@/pages/food/hooks/useFoodData'
|
||||
import { Pages } from '@/config/Pages'
|
||||
import type { ErrorType } from '@/helpers/types'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
import CouponBasicInfo from './components/CouponBasicInfo'
|
||||
import CouponDiscountSettings from './components/CouponDiscountSettings'
|
||||
import CouponUsageSettings from './components/CouponUsageSettings'
|
||||
import CouponSidebar from './components/CouponSidebar'
|
||||
|
||||
const CreateCoupon: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { mutate: createCoupon, isPending: isCreating } = useCreateCoupon()
|
||||
const { data: categoriesData } = useGetCategories()
|
||||
const { data: foodsData } = useGetFoods()
|
||||
|
||||
const [discountType, setDiscountType] = useState<'DIRECT' | 'CODE'>('CODE')
|
||||
const [couponType, setCouponType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE')
|
||||
|
||||
const categories = useMemo(() => categoriesData?.data || [], [categoriesData?.data])
|
||||
const foods = useMemo(() => foodsData?.data || [], [foodsData?.data])
|
||||
|
||||
const formik = useFormik<CreateCouponType>({
|
||||
initialValues: {
|
||||
code: '',
|
||||
name: '',
|
||||
description: '',
|
||||
type: 'PERCENTAGE',
|
||||
value: 0,
|
||||
maxDiscount: null,
|
||||
minOrderAmount: 0,
|
||||
maxUses: 0,
|
||||
maxUsesPerUser: null,
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
isActive: true,
|
||||
categoryIds: [],
|
||||
foodIds: [],
|
||||
},
|
||||
validationSchema: Yup.object().shape({
|
||||
code: Yup.string().required('کد کوپن الزامی است'),
|
||||
name: Yup.string().required('نام کوپن الزامی است'),
|
||||
description: Yup.string(),
|
||||
type: Yup.string().required('نوع کوپن الزامی است'),
|
||||
value: Yup.number().min(0, 'مقدار باید بیشتر از صفر باشد').required('مقدار کوپن الزامی است'),
|
||||
maxDiscount: Yup.number().nullable().min(0, 'حداکثر تخفیف باید بیشتر از صفر باشد'),
|
||||
minOrderAmount: Yup.number().min(0, 'حداقل مبلغ سفارش باید بیشتر از صفر باشد').required('حداقل مبلغ سفارش الزامی است'),
|
||||
maxUses: Yup.number().min(0, 'حداکثر استفاده باید بیشتر از صفر باشد').required('حداکثر استفاده الزامی است'),
|
||||
maxUsesPerUser: Yup.number().nullable().min(0, 'حداکثر استفاده برای هر کاربر باید بیشتر از صفر باشد'),
|
||||
startDate: Yup.string().nullable(),
|
||||
endDate: Yup.string().nullable(),
|
||||
}),
|
||||
onSubmit: (values) => {
|
||||
createCoupon(values, {
|
||||
onSuccess: () => {
|
||||
toast.success('کوپن با موفقیت ایجاد شد')
|
||||
navigate(Pages.coupons.list)
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(extractErrorMessage(error))
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const generateRandomCode = () => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let code = ''
|
||||
for (let i = 0; i < 8; i++) {
|
||||
code += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
formik.setFieldValue('code', code)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<div className='flex justify-between items-center mb-6'>
|
||||
<h1 className='text-lg font-light'>کد تخفیف جدید</h1>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
className='flex gap-4 w-fit px-5'
|
||||
isloading={isCreating}
|
||||
onClick={() => formik.handleSubmit()}
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickCircle color='white' size={20} className='ml-2' />
|
||||
<span>ثبت تخفیف</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-7 mt-7'>
|
||||
<div className='flex-1'>
|
||||
<div className='bg-white rounded-4xl p-8'>
|
||||
<div className='text-lg font-light mb-6'>کد تخفیف جدید</div>
|
||||
|
||||
<CouponBasicInfo
|
||||
formik={formik}
|
||||
discountType={discountType}
|
||||
onDiscountTypeChange={setDiscountType}
|
||||
onGenerateCode={generateRandomCode}
|
||||
/>
|
||||
|
||||
<CouponDiscountSettings
|
||||
formik={formik}
|
||||
couponType={couponType}
|
||||
onCouponTypeChange={setCouponType}
|
||||
/>
|
||||
|
||||
<CouponUsageSettings formik={formik} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='w-80 flex-shrink-0'>
|
||||
<CouponSidebar
|
||||
formik={formik}
|
||||
categories={categories}
|
||||
foods={foods}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCoupon
|
||||
@@ -0,0 +1,86 @@
|
||||
import { type FC, useState, useMemo } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import Input from '@/components/Input'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface Category {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface CategorySelectorProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
categories: Category[]
|
||||
}
|
||||
|
||||
const CategorySelector: FC<CategorySelectorProps> = ({ formik, categories }) => {
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const filteredCategories = useMemo(() => {
|
||||
if (!search) return categories
|
||||
return categories.filter((cat) =>
|
||||
cat.title.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
}, [categories, search])
|
||||
|
||||
const handleToggle = (categoryId: string, checked: boolean) => {
|
||||
const currentIds = formik.values.categoryIds || []
|
||||
if (checked) {
|
||||
formik.setFieldValue('categoryIds', [...currentIds, categoryId])
|
||||
} else {
|
||||
formik.setFieldValue('categoryIds', currentIds.filter((id) => id !== categoryId))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
formik.setFieldValue('categoryIds', filteredCategories.map((cat) => cat.id))
|
||||
} else {
|
||||
formik.setFieldValue('categoryIds', [])
|
||||
}
|
||||
}
|
||||
|
||||
const isAllSelected = useMemo(() => {
|
||||
return filteredCategories.length > 0 &&
|
||||
filteredCategories.every((cat) => formik.values.categoryIds?.includes(cat.id))
|
||||
}, [filteredCategories, formik.values.categoryIds])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='text-sm mb-3'>دسته بندیهایی که شامل تخفیف میشوند</div>
|
||||
<div className='mb-6 border border-border rounded-xl p-4'>
|
||||
<Input
|
||||
variant='search'
|
||||
className='bg-[#EEF0F7]'
|
||||
placeholder='جستجو'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div className='mt-3 space-y-2 max-h-60 overflow-y-auto'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
<label className='text-sm cursor-pointer'>همه</label>
|
||||
</div>
|
||||
{filteredCategories.map((category) => (
|
||||
<div key={category.id} className='flex items-center gap-2'>
|
||||
<Checkbox
|
||||
checked={formik.values.categoryIds?.includes(category.id) || false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleToggle(category.id, checked as boolean)
|
||||
}
|
||||
/>
|
||||
<label className='text-sm cursor-pointer'>{category.title}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CategorySelector
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { type FC } from 'react'
|
||||
import { Refresh } from 'iconsax-react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import Input from '@/components/Input'
|
||||
import Textarea from '@/components/Textarea'
|
||||
import RadioGroup from '@/components/RadioGroup'
|
||||
import Button from '@/components/Button'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface CouponBasicInfoProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
discountType: 'DIRECT' | 'CODE'
|
||||
onDiscountTypeChange: (value: 'DIRECT' | 'CODE') => void
|
||||
onGenerateCode?: () => void
|
||||
}
|
||||
|
||||
const CouponBasicInfo: FC<CouponBasicInfoProps> = ({
|
||||
formik,
|
||||
discountType,
|
||||
onDiscountTypeChange,
|
||||
onGenerateCode,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className='mb-6'>
|
||||
<div className='text-sm mb-3'>نوع تخفیف</div>
|
||||
<RadioGroup
|
||||
items={[
|
||||
{ label: 'تخفیف مستقیم', value: 'DIRECT' },
|
||||
{ label: 'کد تخفیف', value: 'CODE' },
|
||||
]}
|
||||
selected={discountType}
|
||||
onChange={(value) => onDiscountTypeChange(value as 'DIRECT' | 'CODE')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mb-6'>
|
||||
<div className='flex items-end gap-2'>
|
||||
<div className='flex-1'>
|
||||
<Input
|
||||
label='عنوان کد'
|
||||
placeholder=''
|
||||
name='name'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.name && formik.errors.name ? formik.errors.name : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mb-6'>
|
||||
<Textarea
|
||||
label='توضیحات'
|
||||
placeholder='توضیحات کوپن را وارد کنید'
|
||||
name='description'
|
||||
value={formik.values.description}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.description && formik.errors.description ? formik.errors.description : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mb-6'>
|
||||
<div className='flex items-end gap-2'>
|
||||
<div className='flex-1'>
|
||||
<Input
|
||||
label='کد تخفیف'
|
||||
placeholder=''
|
||||
name='code'
|
||||
value={formik.values.code}
|
||||
onChange={formik.handleChange}
|
||||
error_text={formik.touched.code && formik.errors.code ? formik.errors.code : ''}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className='w-auto bg-transparent px-4 h-10 text-black border border-primary'
|
||||
onClick={onGenerateCode}
|
||||
>
|
||||
<Refresh color='black' size={20} className='ml-2' />
|
||||
ساخت کد
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CouponBasicInfo
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { type FC } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import Input from '@/components/Input'
|
||||
import RadioGroup from '@/components/RadioGroup'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface CouponDiscountSettingsProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
couponType: 'PERCENTAGE' | 'FIXED'
|
||||
onCouponTypeChange: (value: 'PERCENTAGE' | 'FIXED') => void
|
||||
}
|
||||
|
||||
const CouponDiscountSettings: FC<CouponDiscountSettingsProps> = ({
|
||||
formik,
|
||||
couponType,
|
||||
onCouponTypeChange,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className='mb-6'>
|
||||
<div className='text-sm mb-3'>مبلغ تخفیف</div>
|
||||
<RadioGroup
|
||||
items={[
|
||||
{ label: 'مبلغ ثابت', value: 'FIXED' },
|
||||
{ label: 'درصد', value: 'PERCENTAGE' },
|
||||
]}
|
||||
selected={couponType}
|
||||
onChange={(value) => {
|
||||
onCouponTypeChange(value as 'PERCENTAGE' | 'FIXED')
|
||||
formik.setFieldValue('type', value as 'PERCENTAGE' | 'FIXED')
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mb-6'>
|
||||
<Input
|
||||
label={couponType === 'PERCENTAGE' ? 'درصد تخفیف' : 'مبلغ تخفیف'}
|
||||
placeholder={couponType === 'PERCENTAGE' ? '%' : 'تومان'}
|
||||
name='value'
|
||||
type='number'
|
||||
value={formik.values.value}
|
||||
onChange={(e) => formik.setFieldValue('value', Number(e.target.value))}
|
||||
error_text={formik.touched.value && formik.errors.value ? formik.errors.value : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{couponType === 'PERCENTAGE' && (
|
||||
<div className='mb-6'>
|
||||
<Input
|
||||
label='حداکثر تخفیف'
|
||||
placeholder='تومان'
|
||||
name='maxDiscount'
|
||||
type='number'
|
||||
seprator={true}
|
||||
value={formik.values.maxDiscount || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value ? Number(e.target.value) : null
|
||||
formik.setFieldValue('maxDiscount', value)
|
||||
}}
|
||||
error_text={formik.touched.maxDiscount && formik.errors.maxDiscount ? formik.errors.maxDiscount : ''}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='mb-6'>
|
||||
<Input
|
||||
label='حداقل مبلغ سفارش'
|
||||
placeholder='تومان'
|
||||
name='minOrderAmount'
|
||||
type='number'
|
||||
seprator={true}
|
||||
value={formik.values.minOrderAmount}
|
||||
onChange={(e) => formik.setFieldValue('minOrderAmount', Number(e.target.value))}
|
||||
error_text={formik.touched.minOrderAmount && formik.errors.minOrderAmount ? formik.errors.minOrderAmount : ''}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CouponDiscountSettings
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { type FC } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import SwitchComponent from '@/components/Switch'
|
||||
import CategorySelector from './CategorySelector'
|
||||
import FoodSelector from './FoodSelector'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface Category {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface Food {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface CouponSidebarProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
categories: Category[]
|
||||
foods: Food[]
|
||||
}
|
||||
|
||||
const CouponSidebar: FC<CouponSidebarProps> = ({ formik, categories, foods }) => {
|
||||
return (
|
||||
<div className='bg-white rounded-4xl p-6'>
|
||||
<div className='mb-6'>
|
||||
<div className='text-sm mb-3'>وضعیت تخفیف</div>
|
||||
<SwitchComponent
|
||||
active={formik.values.isActive}
|
||||
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||
label={formik.values.isActive ? 'فعال' : 'غیرفعال'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CategorySelector formik={formik} categories={categories} />
|
||||
<FoodSelector formik={formik} foods={foods} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CouponSidebar
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { type FC } from 'react'
|
||||
import moment from 'moment-jalaali'
|
||||
import type { FormikProps } from 'formik'
|
||||
import Input from '@/components/Input'
|
||||
import DatePickerComponent from '@/components/DatePicker'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface CouponUsageSettingsProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
}
|
||||
|
||||
const CouponUsageSettings: FC<CouponUsageSettingsProps> = ({ formik }) => {
|
||||
const convertToGregorian = (persianDate: string): string | null => {
|
||||
if (!persianDate) return null
|
||||
const gregorianDate = moment(persianDate, 'jYYYY-jMM-jDD').format('YYYY-MM-DD')
|
||||
return gregorianDate
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='grid grid-cols-2 gap-4 mb-6'>
|
||||
<Input
|
||||
label='حداکثر استفاده'
|
||||
placeholder=''
|
||||
name='maxUses'
|
||||
type='number'
|
||||
value={formik.values.maxUses}
|
||||
onChange={(e) => formik.setFieldValue('maxUses', Number(e.target.value))}
|
||||
error_text={formik.touched.maxUses && formik.errors.maxUses ? formik.errors.maxUses : ''}
|
||||
/>
|
||||
<Input
|
||||
label='حداکثر استفاده برای هر کاربر'
|
||||
placeholder=''
|
||||
name='maxUsesPerUser'
|
||||
type='number'
|
||||
value={formik.values.maxUsesPerUser || ''}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value ? Number(e.target.value) : null
|
||||
formik.setFieldValue('maxUsesPerUser', value)
|
||||
}}
|
||||
error_text={formik.touched.maxUsesPerUser && formik.errors.maxUsesPerUser ? formik.errors.maxUsesPerUser : ''}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<DatePickerComponent
|
||||
label='تاریخ شروع'
|
||||
placeholder='تاریخ شروع را انتخاب کنید'
|
||||
onChange={(date) => {
|
||||
const gregorianDate = convertToGregorian(date)
|
||||
formik.setFieldValue('startDate', gregorianDate)
|
||||
}}
|
||||
/>
|
||||
<DatePickerComponent
|
||||
label='تاریخ پایان'
|
||||
placeholder='تاریخ پایان را انتخاب کنید'
|
||||
onChange={(date) => {
|
||||
const gregorianDate = convertToGregorian(date)
|
||||
formik.setFieldValue('endDate', gregorianDate)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CouponUsageSettings
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { type FC, useState, useMemo } from 'react'
|
||||
import type { FormikProps } from 'formik'
|
||||
import Input from '@/components/Input'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import type { CreateCouponType } from '../types/Types'
|
||||
|
||||
interface Food {
|
||||
id: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface FoodSelectorProps {
|
||||
formik: FormikProps<CreateCouponType>
|
||||
foods: Food[]
|
||||
}
|
||||
|
||||
const FoodSelector: FC<FoodSelectorProps> = ({ formik, foods }) => {
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const filteredFoods = useMemo(() => {
|
||||
if (!search) return foods
|
||||
return foods.filter((food) =>
|
||||
food.title.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
}, [foods, search])
|
||||
|
||||
const handleToggle = (foodId: string, checked: boolean) => {
|
||||
const currentIds = formik.values.foodIds || []
|
||||
if (checked) {
|
||||
formik.setFieldValue('foodIds', [...currentIds, foodId])
|
||||
} else {
|
||||
formik.setFieldValue('foodIds', currentIds.filter((id) => id !== foodId))
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
formik.setFieldValue('foodIds', filteredFoods.map((food) => food.id))
|
||||
} else {
|
||||
formik.setFieldValue('foodIds', [])
|
||||
}
|
||||
}
|
||||
|
||||
const isAllSelected = useMemo(() => {
|
||||
return filteredFoods.length > 0 &&
|
||||
filteredFoods.every((food) => formik.values.foodIds?.includes(food.id))
|
||||
}, [filteredFoods, formik.values.foodIds])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='text-sm mb-3'>غذاهایی که شامل تخفیف میشوند</div>
|
||||
<div className='mb-6 border border-border rounded-xl p-4'>
|
||||
<Input
|
||||
variant='search'
|
||||
className='bg-[#EEF0F7]'
|
||||
placeholder='جستجو'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
<div className='mt-3 space-y-2 max-h-60 overflow-y-auto'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleSelectAll}
|
||||
/>
|
||||
<label className='text-sm cursor-pointer'>همه</label>
|
||||
</div>
|
||||
{filteredFoods.map((food) => (
|
||||
<div key={food.id} className='flex items-center gap-2'>
|
||||
<Checkbox
|
||||
checked={formik.values.foodIds?.includes(food.id) || false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleToggle(food.id, checked as boolean)
|
||||
}
|
||||
/>
|
||||
<label className='text-sm cursor-pointer'>{food.title}</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FoodSelector
|
||||
|
||||
@@ -19,3 +19,15 @@ export const useDeleteCoupon = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateCoupon = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: api.createCoupon,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["coupons"],
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import axios from "@/config/axios";
|
||||
import type { IGetCouponsResponse } from "../types/Types";
|
||||
import type { CreateCouponType, IGetCouponsResponse } from "../types/Types";
|
||||
|
||||
export const getCoupons = async (): Promise<IGetCouponsResponse> => {
|
||||
const { data } = await axios.get<IGetCouponsResponse>("/admin/coupons");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createCoupon = async (params: CreateCouponType) => {
|
||||
const { data } = await axios.post("/admin/coupons", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteCoupon = async (id: string) => {
|
||||
const { data } = await axios.delete(`/admin/coupons/${id}`);
|
||||
return data;
|
||||
|
||||
@@ -25,3 +25,20 @@ export interface ICoupon {
|
||||
}
|
||||
|
||||
export type IGetCouponsResponse = IResponse<ICoupon[]>;
|
||||
|
||||
export type CreateCouponType = {
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: CouponType;
|
||||
value: number;
|
||||
maxDiscount: number | null;
|
||||
minOrderAmount: number;
|
||||
maxUses: number;
|
||||
maxUsesPerUser: number | null;
|
||||
startDate: string | null;
|
||||
endDate: string | null;
|
||||
isActive: boolean;
|
||||
categoryIds?: string[];
|
||||
foodIds?: string[];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user