add coupon + success + failed Page
This commit is contained in:
@@ -1,32 +1,50 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Input from '@/components/Input'
|
||||
import { FC, useState } from 'react'
|
||||
import { useAddCoupon, useGetCart } from '../hooks/useCartData'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { extractErrorMessage } from '@/helpers/errorUtils'
|
||||
|
||||
type DiscountCardProps = {
|
||||
discountCode: string
|
||||
setDiscountCode: (code: string) => void
|
||||
onSubmit: () => void
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
const DiscountCard: FC<DiscountCardProps> = ({
|
||||
discountCode,
|
||||
setDiscountCode,
|
||||
onSubmit,
|
||||
isLoading = false
|
||||
}) => {
|
||||
|
||||
const DiscountCard: FC = () => {
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const [couponCode, setCouponCode] = useState('')
|
||||
const addCouponMutation = useAddCoupon()
|
||||
const { data: cartData, refetch } = useGetCart()
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
onSubmit()
|
||||
|
||||
if (!couponCode.trim()) {
|
||||
toast('لطفاً کد کوپن را وارد کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
await addCouponMutation.mutateAsync({
|
||||
code: couponCode,
|
||||
cartShipmentItemId: cartData?.results?.cart?.cartShipmentItems?.[0]._id as string
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
toast('کوپن با موفقیت اعمال شد', 'success')
|
||||
refetch()
|
||||
setCouponCode('')
|
||||
setIsExpanded(false)
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!isExpanded) {
|
||||
return (
|
||||
<div className='h-16 border border-border rounded-2xl flex justify-between mt-5 items-center px-6'>
|
||||
<div>
|
||||
کد تخفیف دارید؟
|
||||
کد کوپن دارید؟
|
||||
</div>
|
||||
|
||||
<Button
|
||||
@@ -42,32 +60,33 @@ const DiscountCard: FC<DiscountCardProps> = ({
|
||||
|
||||
return (
|
||||
<div className='border border-border rounded-2xl mt-5 p-4 sm:p-6'>
|
||||
<div className='text-sm font-medium mb-4'>کد تخفیف</div>
|
||||
<div className='text-sm font-medium mb-4'>کد کوپن</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className='flex gap-3'>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="کد تخفیف خود را وارد کنید"
|
||||
value={discountCode}
|
||||
onChange={(e) => setDiscountCode(e.target.value)}
|
||||
placeholder="کد کوپن خود را وارد کنید"
|
||||
value={couponCode}
|
||||
onChange={(e) => setCouponCode(e.target.value)}
|
||||
className="flex-1"
|
||||
disabled={isLoading}
|
||||
disabled={addCouponMutation.isPending}
|
||||
label=""
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isLoading || !discountCode.trim()}
|
||||
disabled={addCouponMutation.isPending || !couponCode.trim()}
|
||||
className="px-6"
|
||||
>
|
||||
{isLoading ? 'در حال اعمال...' : 'اعمال'}
|
||||
{addCouponMutation.isPending ? 'در حال اعمال...' : 'اعمال'}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setIsExpanded(false)
|
||||
setDiscountCode('')
|
||||
setCouponCode('')
|
||||
}}
|
||||
disabled={addCouponMutation.isPending}
|
||||
className="px-4"
|
||||
>
|
||||
لغو
|
||||
|
||||
@@ -110,3 +110,9 @@ export const useSaveShipmentCart = () => {
|
||||
mutationFn: api.saveShipmentCart,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddCoupon = () => {
|
||||
return useMutation({
|
||||
mutationFn: api.addCoupon,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NextPage } from 'next'
|
||||
import TitleBack from '../components/TitleBack'
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||
import DiscountCard from '../components/DiscountCard'
|
||||
import { usePaymentMethods, useGetCart, useShippingCost, useApplyDiscountCode, useCheckoutCart } from '../hooks/useCartData'
|
||||
import { usePaymentMethods, useGetCart, useShippingCost, useCheckoutCart } from '../hooks/useCartData'
|
||||
import { useState } from 'react'
|
||||
import { toast } from '@/components/Toast'
|
||||
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
|
||||
@@ -16,11 +16,9 @@ const CartPayment: NextPage = () => {
|
||||
const { data: shippingData, isLoading: shippingLoading } = useShippingCost()
|
||||
const { data: profileData, isLoading: profileLoading } = useGetProfile()
|
||||
|
||||
const applyDiscountMutation = useApplyDiscountCode()
|
||||
const checkoutMutation = useCheckoutCart()
|
||||
|
||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>('')
|
||||
const [discountCode, setDiscountCode] = useState<string>('')
|
||||
|
||||
const isLoading = paymentMethodsLoading || cartLoading || shippingLoading || profileLoading
|
||||
|
||||
@@ -66,22 +64,6 @@ const CartPayment: NextPage = () => {
|
||||
setSelectedPaymentMethod(value)
|
||||
}
|
||||
|
||||
const handleDiscountCodeSubmit = async () => {
|
||||
if (!discountCode.trim()) {
|
||||
toast('لطفاً کد تخفیف را وارد کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await applyDiscountMutation.mutateAsync({ discount_code: discountCode })
|
||||
toast('کد تخفیف با موفقیت اعمال شد', 'success')
|
||||
setDiscountCode('')
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = (error as { response?: { data?: { message?: string } } })?.response?.data?.message || 'کد تخفیف نامعتبر است'
|
||||
toast(errorMessage, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayment = async () => {
|
||||
if (!selectedPaymentMethod) {
|
||||
toast('لطفاً روش پرداخت را انتخاب کنید', 'error')
|
||||
@@ -109,12 +91,9 @@ const CartPayment: NextPage = () => {
|
||||
<div className="mt-6 flex flex-col lg:flex-row gap-6">
|
||||
<div className='flex-1'>
|
||||
<TitleBack title='پرداخت' />
|
||||
<DiscountCard
|
||||
discountCode={discountCode}
|
||||
setDiscountCode={setDiscountCode}
|
||||
onSubmit={handleDiscountCodeSubmit}
|
||||
isLoading={applyDiscountMutation.isPending}
|
||||
/>
|
||||
{shippingData?.results?.shipping?.map((shop) => (
|
||||
<DiscountCard key={shop.shopId} />
|
||||
))}
|
||||
<div className='mt-5 border border-border rounded-2xl p-4 sm:p-6 font-light'>
|
||||
<div>انتخاب روش پرداخت</div>
|
||||
|
||||
@@ -151,7 +130,7 @@ const CartPayment: NextPage = () => {
|
||||
<CartSummary
|
||||
itemsCount={cart?.items_count || 0}
|
||||
itemsPrice={cart?.retail_price || 0}
|
||||
discount={cart?.total_discount || 0}
|
||||
discount={cart.total_discount}
|
||||
shippingCost={totalShippingCost}
|
||||
total={finalTotal}
|
||||
onConfirm={handlePayment}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
BulkAddCartType,
|
||||
ShipmentCartResponseType,
|
||||
ShipmentSaveType,
|
||||
AddCouponType,
|
||||
} from "../types/Types";
|
||||
|
||||
export const getCart = async (): Promise<CartResponseType> => {
|
||||
@@ -87,3 +88,8 @@ export const saveShipmentCart = async (params: ShipmentSaveType) => {
|
||||
const { data } = await axios.post("/shipment/save", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const addCoupon = async (params: AddCouponType) => {
|
||||
const { data } = await axios.post("/coupon/add-coupon", params);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -79,6 +79,35 @@ export type CartItemType = {
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
// Cart Shipment Item Type
|
||||
export type CartShipmentItemType = {
|
||||
_id: string;
|
||||
cart: string;
|
||||
shop: string;
|
||||
shipmentItems: Array<{
|
||||
product: number;
|
||||
variant: string;
|
||||
quantity: number;
|
||||
cancelled_quantity: number;
|
||||
returned_quantity: number;
|
||||
selling_price: number;
|
||||
retail_price: number;
|
||||
discount_percent: number;
|
||||
shipmentCost: number;
|
||||
isFreeShip: boolean;
|
||||
_id: string;
|
||||
}>;
|
||||
shipper: number;
|
||||
totalSellingPrice: number;
|
||||
totalRetailPrice: number;
|
||||
totalShipmentCost: number;
|
||||
totalCouponDiscount: number;
|
||||
totalPaymentPrice: number;
|
||||
itemsCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
// Cart Types
|
||||
export type CartType = {
|
||||
_id: string;
|
||||
@@ -95,11 +124,12 @@ export type CartType = {
|
||||
dateOfBirth: string | null;
|
||||
address: {
|
||||
_id: string;
|
||||
};
|
||||
} | null;
|
||||
nationalCode: string | null;
|
||||
homeNumber: string | null;
|
||||
};
|
||||
isWholeSale: boolean;
|
||||
cartShipmentItems: CartShipmentItemType[];
|
||||
};
|
||||
|
||||
// Recommended Product Type
|
||||
@@ -433,3 +463,8 @@ export type ShipmentCartResponseType = {
|
||||
recommended: RecommendedProductType[];
|
||||
};
|
||||
};
|
||||
|
||||
export type AddCouponType = {
|
||||
code: string;
|
||||
cartShipmentItemId: string;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
'use client'
|
||||
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { TickCircle, CloseCircle } from 'iconsax-react'
|
||||
import { Suspense } from 'react'
|
||||
|
||||
const ThankYouContent = () => {
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const status = searchParams.get('status')
|
||||
const orderId = searchParams.get('order_id')
|
||||
const paymentId = searchParams.get('payment_id')
|
||||
|
||||
// صفحه پرداخت ناموفق
|
||||
if (status === 'failed') {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh] px-4 py-8">
|
||||
<div className="text-center max-w-lg w-full">
|
||||
{/* آیکون خطا */}
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="bg-red-50 rounded-full p-5">
|
||||
<CloseCircle color="red" size={64} className="text-red-500" variant="Bold" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* پیام اصلی */}
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
پرداخت ناموفق بود
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-8">
|
||||
متأسفانه پرداخت شما با خطا مواجه شد
|
||||
</p>
|
||||
|
||||
{/* اطلاعات */}
|
||||
{orderId && (
|
||||
<div className="bg-gray-50 rounded-xl p-6 mb-6 text-right">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">شماره سفارش:</span>
|
||||
<span className="font-bold text-gray-900">{orderId}</span>
|
||||
</div>
|
||||
{paymentId && (
|
||||
<div className="flex justify-between items-center pt-4 border-t border-gray-200">
|
||||
<span className="text-sm text-gray-600">کد پیگیری:</span>
|
||||
<span className="text-sm text-gray-800 font-mono">{paymentId}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* دکمههای اکشن */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button asChild className="w-full">
|
||||
<Link href="/cart/payment">تلاش مجدد برای پرداخت</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href="/">بازگشت به صفحه اصلی</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// صفحه پرداخت موفق
|
||||
if (status === 'success') {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh] px-4 py-8">
|
||||
<div className="text-center max-w-lg w-full">
|
||||
{/* آیکون موفقیت */}
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="bg-green-50 rounded-full p-5">
|
||||
<TickCircle size={64} className="text-green-500" variant="Bold" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* پیام اصلی */}
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
پرداخت با موفقیت انجام شد
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-8">
|
||||
سفارش شما ثبت شد و به زودی ارسال میشود
|
||||
</p>
|
||||
|
||||
{/* اطلاعات سفارش */}
|
||||
<div className="bg-gray-50 rounded-xl p-6 mb-6 text-right">
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-600">شماره سفارش:</span>
|
||||
<span className="font-bold text-gray-900">{orderId}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center pt-4 border-t border-gray-200">
|
||||
<span className="text-sm text-gray-600">کد پیگیری:</span>
|
||||
<span className="text-sm text-gray-800 font-mono">{paymentId}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* دکمههای اکشن */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<Button asChild className="w-full">
|
||||
<Link href="/profile/orders">مشاهده سفارشات</Link>
|
||||
</Button>
|
||||
<Button asChild variant="outline" className="w-full">
|
||||
<Link href="/">بازگشت به صفحه اصلی</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// در صورتی که status معتبر نباشد
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="text-center max-w-md w-full px-4">
|
||||
<div className="text-gray-600 text-lg mb-4">
|
||||
اطلاعات پرداخت نامعتبر است
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link href="/">بازگشت به صفحه اصلی</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ThankYouPage = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<ThankYouContent />
|
||||
</Suspense>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThankYouPage
|
||||
@@ -145,7 +145,7 @@ const Cart = () => {
|
||||
</div>
|
||||
|
||||
{/* View Cart Button */}
|
||||
{itemsCount > 0 && (
|
||||
{itemsCount > 0 && isLogin && (
|
||||
<Link href="/cart" className='block'>
|
||||
<Button className='w-full bg-primary hover:bg-primary/90 text-white rounded-lg py-3'>
|
||||
مشاهده سبد خرید
|
||||
|
||||
Reference in New Issue
Block a user