apply discount

This commit is contained in:
hamid zarghami
2025-12-07 12:23:15 +03:30
parent 88088c2ed5
commit fea922a2fa
6 changed files with 144 additions and 43 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -16,6 +16,12 @@ export interface CartItem {
totalPrice: number; totalPrice: number;
} }
export interface Coupon {
couponId: string;
couponName: string;
couponCode: string;
}
export interface CartData { export interface CartData {
userId: string; userId: string;
restaurantId: string; restaurantId: string;
@@ -34,6 +40,7 @@ export interface CartData {
deliveryMethodId?: string; deliveryMethodId?: string;
addressId?: string; addressId?: string;
paymentMethodId?: string; paymentMethodId?: string;
coupon?: Coupon;
} }
export interface CartCategory { export interface CartCategory {
@@ -1,10 +1,15 @@
'use client'; 'use client';
import Combobox, { ComboboxOption } from '@/components/combobox/Combobox';
import InputField from '@/components/input/InputField'; import InputField from '@/components/input/InputField';
import clsx from 'clsx'; 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 { 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 = { type CouponSectionProps = {
couponType: string; couponType: string;
@@ -21,26 +26,58 @@ export const CouponSection = ({
onCouponTypeChange, onCouponTypeChange,
onCouponCodeChange, onCouponCodeChange,
}: CouponSectionProps) => { }: 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 }> = [ const couponOptions: Array<{ id: string, label: string, icon: Icon }> = [
{ id: '0', label: t("SectionCoupon.InputCouponType.Options.Discount"), icon: Cup }, { id: '0', label: t("SectionCoupon.InputCouponType.Options.Discount"), icon: Cup },
{ id: '1', label: t("SectionCoupon.InputCouponType.Options.GiftCard"), icon: Ticket }, { id: '1', label: t("SectionCoupon.InputCouponType.Options.GiftCard"), icon: Ticket },
]; ];
const couponDropdownOptions: Array<ComboboxOption> = [ const handleApplyCoupon = () => {
{ id: '0', title: t("SectionCoupon.InputCouponCode.Options.Default") }, applyCoupon(couponCode, {
{ id: '1', title: "Something" }, onSuccess: () => {
]; toast('کوپن با موفقیت اعمال شد', 'success');
setIsApplied(true);
const handleCouponCodeChange = (id: string) => { },
if (id === '0') { onError: (error) => {
onCouponCodeChange(''); toast(extractErrorMessage(error), 'error');
return; setIsApplied(false);
} }
onCouponCodeChange(id); });
}; };
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 ( return (
<section className="bg-container rounded-container box-shadow-normal p-4"> <section className="bg-container rounded-container box-shadow-normal p-4">
<div className="py-3"> <div className="py-3">
@@ -57,7 +94,7 @@ export const CouponSection = ({
/> />
<span className='text-xs mt-0.5'>{label}</span> <span className='text-xs mt-0.5'>{label}</span>
</label> </label>
<input <input
checked={couponType === id} checked={couponType === id}
onChange={() => onCouponTypeChange(id)} onChange={() => onCouponTypeChange(id)}
@@ -69,33 +106,50 @@ export const CouponSection = ({
))} ))}
</div> </div>
<Combobox {isApplied ? (
className={clsx( <div className='mt-6 flex items-center gap-3'>
'relative mt-6', <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'>
couponType === '0' ? 'block' : 'hidden', <TickCircle size={20} color='#10b981' variant='Bold' />
couponCode.length === 0 && 'text-border **:stroke-border dark:text-neutral-500 dark:**:stroke-neutral-500' <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>
icon={TicketDiscount} </div>
placeholder={t("SectionCoupon.InputCouponCode.Placeholder")} <button
title='' onClick={handleRemoveCoupon}
searchable={false} disabled={isRemoving}
options={couponDropdownOptions} className='p-2.5 hover:bg-gray-100 dark:hover:bg-neutral-800 rounded-normal transition-colors disabled:opacity-50'
selectedId={couponCode} type='button'
onSelectionChange={(e, i) => handleCouponCodeChange(String(i))} >
/> <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> </div>
</section> </section>
); );
@@ -79,6 +79,12 @@ function OrderDetailInex() {
} }
}, [cartItems?.data?.addressId, setAddressCart, setIsSelectedAddress, setSelectedAddressId]); }, [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(() => { const selectedDeliveryMethod = useMemo(() => {
if (!shipmentMethod?.data || !deliveryType) return null; if (!shipmentMethod?.data || !deliveryType) return null;
return shipmentMethod.data.find((item) => item.id === deliveryType); 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 { import {
getPaymentMethod, getPaymentMethod,
getShipmentMethod, getShipmentMethod,
@@ -6,6 +6,8 @@ import {
setDeliveryMethod, setDeliveryMethod,
setPaymentMethod, setPaymentMethod,
createOrder, createOrder,
applyCoupon,
removeCoupon,
} from "../service/OrderService"; } from "../service/OrderService";
export const useGetShipmentMethod = () => { export const useGetShipmentMethod = () => {
@@ -45,3 +47,25 @@ export const useCreateOrder = () => {
mutationFn: createOrder, 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"); const { data } = await api.post<CreateOrderResponse>("/public/checkout");
return data; 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;
};