apply discount
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
@@ -16,6 +16,12 @@ export interface CartItem {
|
||||
totalPrice: number;
|
||||
}
|
||||
|
||||
export interface Coupon {
|
||||
couponId: string;
|
||||
couponName: string;
|
||||
couponCode: string;
|
||||
}
|
||||
|
||||
export interface CartData {
|
||||
userId: string;
|
||||
restaurantId: string;
|
||||
@@ -34,6 +40,7 @@ export interface CartData {
|
||||
deliveryMethodId?: string;
|
||||
addressId?: string;
|
||||
paymentMethodId?: string;
|
||||
coupon?: Coupon;
|
||||
}
|
||||
|
||||
export interface CartCategory {
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import Combobox, { ComboboxOption } from '@/components/combobox/Combobox';
|
||||
import InputField from '@/components/input/InputField';
|
||||
import clsx from 'clsx';
|
||||
import { Cup, Icon, Ticket, TicketDiscount } from 'iconsax-react';
|
||||
import { Cup, Icon, TickCircle, Ticket, Trash } from 'iconsax-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useApplyCoupon, useRemoveCoupon } from '../../hooks/useOrderData';
|
||||
import Button from '@/components/button/PrimaryButton';
|
||||
import { extractErrorMessage } from '@/lib/func';
|
||||
import { toast } from '@/components/Toast';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useGetCartItems } from '@/app/[name]/(Dialogs)/cart/hooks/useCartData';
|
||||
|
||||
type CouponSectionProps = {
|
||||
couponType: string;
|
||||
@@ -21,26 +26,58 @@ export const CouponSection = ({
|
||||
onCouponTypeChange,
|
||||
onCouponCodeChange,
|
||||
}: CouponSectionProps) => {
|
||||
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
|
||||
|
||||
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
|
||||
const { mutate: applyCoupon, isPending } = useApplyCoupon();
|
||||
const { mutate: removeCoupon, isPending: isRemoving } = useRemoveCoupon();
|
||||
const { data: cartItems } = useGetCartItems();
|
||||
const [isApplied, setIsApplied] = useState<boolean>(false);
|
||||
const couponOptions: Array<{ id: string, label: string, icon: Icon }> = [
|
||||
{ id: '0', label: t("SectionCoupon.InputCouponType.Options.Discount"), icon: Cup },
|
||||
{ id: '1', label: t("SectionCoupon.InputCouponType.Options.GiftCard"), icon: Ticket },
|
||||
];
|
||||
|
||||
const couponDropdownOptions: Array<ComboboxOption> = [
|
||||
{ id: '0', title: t("SectionCoupon.InputCouponCode.Options.Default") },
|
||||
{ id: '1', title: "Something" },
|
||||
];
|
||||
|
||||
const handleCouponCodeChange = (id: string) => {
|
||||
if (id === '0') {
|
||||
onCouponCodeChange('');
|
||||
return;
|
||||
}
|
||||
onCouponCodeChange(id);
|
||||
const handleApplyCoupon = () => {
|
||||
applyCoupon(couponCode, {
|
||||
onSuccess: () => {
|
||||
toast('کوپن با موفقیت اعمال شد', 'success');
|
||||
setIsApplied(true);
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), 'error');
|
||||
setIsApplied(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleCouponCodeChange = (value: string) => {
|
||||
onCouponCodeChange(value);
|
||||
if (isApplied) {
|
||||
setIsApplied(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveCoupon = () => {
|
||||
removeCoupon(undefined, {
|
||||
onSuccess: () => {
|
||||
toast('کوپن با موفقیت حذف شد', 'success');
|
||||
setIsApplied(false);
|
||||
onCouponCodeChange('');
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), 'error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const hasCoupon = (cartItems?.data?.couponDiscount ?? 0) > 0;
|
||||
setIsApplied(hasCoupon);
|
||||
if (hasCoupon && cartItems?.data?.coupon?.couponCode) {
|
||||
onCouponCodeChange(cartItems.data.coupon.couponCode);
|
||||
}
|
||||
}, [cartItems, onCouponCodeChange]);
|
||||
|
||||
return (
|
||||
<section className="bg-container rounded-container box-shadow-normal p-4">
|
||||
<div className="py-3">
|
||||
@@ -57,7 +94,7 @@ export const CouponSection = ({
|
||||
/>
|
||||
<span className='text-xs mt-0.5'>{label}</span>
|
||||
</label>
|
||||
|
||||
|
||||
<input
|
||||
checked={couponType === id}
|
||||
onChange={() => onCouponTypeChange(id)}
|
||||
@@ -69,33 +106,50 @@ export const CouponSection = ({
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Combobox
|
||||
className={clsx(
|
||||
'relative mt-6',
|
||||
couponType === '0' ? 'block' : 'hidden',
|
||||
couponCode.length === 0 && 'text-border **:stroke-border dark:text-neutral-500 dark:**:stroke-neutral-500'
|
||||
)}
|
||||
icon={TicketDiscount}
|
||||
placeholder={t("SectionCoupon.InputCouponCode.Placeholder")}
|
||||
title=''
|
||||
searchable={false}
|
||||
options={couponDropdownOptions}
|
||||
selectedId={couponCode}
|
||||
onSelectionChange={(e, i) => handleCouponCodeChange(String(i))}
|
||||
/>
|
||||
{isApplied ? (
|
||||
<div className='mt-6 flex items-center gap-3'>
|
||||
<div className='flex-1 flex items-center gap-2 px-4 py-2.5 bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800 rounded-normal'>
|
||||
<TickCircle size={20} color='#10b981' variant='Bold' />
|
||||
<span className='text-xs text-emerald-700 dark:text-emerald-400 font-medium'>کد تخفیف اعمال شده</span>
|
||||
<span className='text-xs text-emerald-600 dark:text-emerald-500 font-medium mr-auto'>{couponCode}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleRemoveCoupon}
|
||||
disabled={isRemoving}
|
||||
className='p-2.5 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-normal transition-colors disabled:opacity-50'
|
||||
type='button'
|
||||
>
|
||||
<Trash color='currentColor' size={20} className='text-gray-600 dark:text-gray-400' />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex gap-4 items-end'>
|
||||
<InputField
|
||||
autoComplete='off'
|
||||
className={clsx(
|
||||
'relative mt-6',
|
||||
couponCodeError.trim() === 'valid' && 'text-emerald-400 **:stroke-emerald-400 ring ring-emerald-400'
|
||||
)}
|
||||
placeholder={t("SectionCoupon.InputCouponCode.Placeholder")}
|
||||
htmlFor={'couponCode'}
|
||||
onChange={(e) => handleCouponCodeChange(e.target.value)}
|
||||
value={couponCode}
|
||||
/>
|
||||
<Button
|
||||
disabled={couponCode.length === 0 || isPending}
|
||||
className='w-fit h-11'
|
||||
onClick={handleApplyCoupon}
|
||||
pending={isPending}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<TickCircle size={16} color='currentColor' variant='Outline' />
|
||||
<span className='text-xs whitespace-nowrap'>اعمال</span>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<InputField
|
||||
autoComplete='off'
|
||||
className={clsx(
|
||||
'relative mt-6',
|
||||
couponType === '1' ? '' : 'hidden!',
|
||||
couponCodeError.trim() === 'valid' && 'text-emerald-400 **:stroke-emerald-400 ring ring-emerald-400'
|
||||
)}
|
||||
placeholder={t("SectionCoupon.InputCouponCode.Placeholder")}
|
||||
htmlFor={'couponCode'}
|
||||
onChange={(e) => onCouponCodeChange(e.target.value)}
|
||||
value={couponCode}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -79,6 +79,12 @@ function OrderDetailInex() {
|
||||
}
|
||||
}, [cartItems?.data?.addressId, setAddressCart, setIsSelectedAddress, setSelectedAddressId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cartItems?.data?.coupon?.couponCode && cartItems.data.couponDiscount > 0) {
|
||||
setCouponCode(cartItems.data.coupon.couponCode);
|
||||
}
|
||||
}, [cartItems?.data?.coupon?.couponCode, cartItems?.data?.couponDiscount]);
|
||||
|
||||
const selectedDeliveryMethod = useMemo(() => {
|
||||
if (!shipmentMethod?.data || !deliveryType) return null;
|
||||
return shipmentMethod.data.find((item) => item.id === deliveryType);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
getPaymentMethod,
|
||||
getShipmentMethod,
|
||||
@@ -6,6 +6,8 @@ import {
|
||||
setDeliveryMethod,
|
||||
setPaymentMethod,
|
||||
createOrder,
|
||||
applyCoupon,
|
||||
removeCoupon,
|
||||
} from "../service/OrderService";
|
||||
|
||||
export const useGetShipmentMethod = () => {
|
||||
@@ -45,3 +47,25 @@ export const useCreateOrder = () => {
|
||||
mutationFn: createOrder,
|
||||
});
|
||||
};
|
||||
|
||||
export const useApplyCoupon = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: applyCoupon,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart-items"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["cart-total"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveCoupon = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: removeCoupon,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["cart-items"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["cart-total"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,3 +42,13 @@ export const createOrder = async (): Promise<CreateOrderResponse> => {
|
||||
const { data } = await api.post<CreateOrderResponse>("/public/checkout");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const applyCoupon = async (code: string) => {
|
||||
const { data } = await api.post("/public/cart/apply-coupon", { code });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const removeCoupon = async () => {
|
||||
const { data } = await api.delete("/public/cart/coupon");
|
||||
return data;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user