add coupon + success + failed Page
This commit is contained in:
@@ -1,32 +1,50 @@
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import { FC, useState } from 'react'
|
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,
|
const DiscountCard: FC = () => {
|
||||||
setDiscountCode,
|
|
||||||
onSubmit,
|
|
||||||
isLoading = false
|
|
||||||
}) => {
|
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
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()
|
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) {
|
if (!isExpanded) {
|
||||||
return (
|
return (
|
||||||
<div className='h-16 border border-border rounded-2xl flex justify-between mt-5 items-center px-6'>
|
<div className='h-16 border border-border rounded-2xl flex justify-between mt-5 items-center px-6'>
|
||||||
<div>
|
<div>
|
||||||
کد تخفیف دارید؟
|
کد کوپن دارید؟
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -42,32 +60,33 @@ const DiscountCard: FC<DiscountCardProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='border border-border rounded-2xl mt-5 p-4 sm:p-6'>
|
<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'>
|
<form onSubmit={handleSubmit} className='flex gap-3'>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="کد تخفیف خود را وارد کنید"
|
placeholder="کد کوپن خود را وارد کنید"
|
||||||
value={discountCode}
|
value={couponCode}
|
||||||
onChange={(e) => setDiscountCode(e.target.value)}
|
onChange={(e) => setCouponCode(e.target.value)}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
disabled={isLoading}
|
disabled={addCouponMutation.isPending}
|
||||||
label=""
|
label=""
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading || !discountCode.trim()}
|
disabled={addCouponMutation.isPending || !couponCode.trim()}
|
||||||
className="px-6"
|
className="px-6"
|
||||||
>
|
>
|
||||||
{isLoading ? 'در حال اعمال...' : 'اعمال'}
|
{addCouponMutation.isPending ? 'در حال اعمال...' : 'اعمال'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsExpanded(false)
|
setIsExpanded(false)
|
||||||
setDiscountCode('')
|
setCouponCode('')
|
||||||
}}
|
}}
|
||||||
|
disabled={addCouponMutation.isPending}
|
||||||
className="px-4"
|
className="px-4"
|
||||||
>
|
>
|
||||||
لغو
|
لغو
|
||||||
|
|||||||
@@ -110,3 +110,9 @@ export const useSaveShipmentCart = () => {
|
|||||||
mutationFn: api.saveShipmentCart,
|
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 TitleBack from '../components/TitleBack'
|
||||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||||
import DiscountCard from '../components/DiscountCard'
|
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 { useState } from 'react'
|
||||||
import { toast } from '@/components/Toast'
|
import { toast } from '@/components/Toast'
|
||||||
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
|
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
|
||||||
@@ -16,11 +16,9 @@ const CartPayment: NextPage = () => {
|
|||||||
const { data: shippingData, isLoading: shippingLoading } = useShippingCost()
|
const { data: shippingData, isLoading: shippingLoading } = useShippingCost()
|
||||||
const { data: profileData, isLoading: profileLoading } = useGetProfile()
|
const { data: profileData, isLoading: profileLoading } = useGetProfile()
|
||||||
|
|
||||||
const applyDiscountMutation = useApplyDiscountCode()
|
|
||||||
const checkoutMutation = useCheckoutCart()
|
const checkoutMutation = useCheckoutCart()
|
||||||
|
|
||||||
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>('')
|
const [selectedPaymentMethod, setSelectedPaymentMethod] = useState<string>('')
|
||||||
const [discountCode, setDiscountCode] = useState<string>('')
|
|
||||||
|
|
||||||
const isLoading = paymentMethodsLoading || cartLoading || shippingLoading || profileLoading
|
const isLoading = paymentMethodsLoading || cartLoading || shippingLoading || profileLoading
|
||||||
|
|
||||||
@@ -66,22 +64,6 @@ const CartPayment: NextPage = () => {
|
|||||||
setSelectedPaymentMethod(value)
|
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 () => {
|
const handlePayment = async () => {
|
||||||
if (!selectedPaymentMethod) {
|
if (!selectedPaymentMethod) {
|
||||||
toast('لطفاً روش پرداخت را انتخاب کنید', 'error')
|
toast('لطفاً روش پرداخت را انتخاب کنید', 'error')
|
||||||
@@ -109,12 +91,9 @@ const CartPayment: NextPage = () => {
|
|||||||
<div className="mt-6 flex flex-col lg:flex-row gap-6">
|
<div className="mt-6 flex flex-col lg:flex-row gap-6">
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<TitleBack title='پرداخت' />
|
<TitleBack title='پرداخت' />
|
||||||
<DiscountCard
|
{shippingData?.results?.shipping?.map((shop) => (
|
||||||
discountCode={discountCode}
|
<DiscountCard key={shop.shopId} />
|
||||||
setDiscountCode={setDiscountCode}
|
))}
|
||||||
onSubmit={handleDiscountCodeSubmit}
|
|
||||||
isLoading={applyDiscountMutation.isPending}
|
|
||||||
/>
|
|
||||||
<div className='mt-5 border border-border rounded-2xl p-4 sm:p-6 font-light'>
|
<div className='mt-5 border border-border rounded-2xl p-4 sm:p-6 font-light'>
|
||||||
<div>انتخاب روش پرداخت</div>
|
<div>انتخاب روش پرداخت</div>
|
||||||
|
|
||||||
@@ -151,7 +130,7 @@ const CartPayment: NextPage = () => {
|
|||||||
<CartSummary
|
<CartSummary
|
||||||
itemsCount={cart?.items_count || 0}
|
itemsCount={cart?.items_count || 0}
|
||||||
itemsPrice={cart?.retail_price || 0}
|
itemsPrice={cart?.retail_price || 0}
|
||||||
discount={cart?.total_discount || 0}
|
discount={cart.total_discount}
|
||||||
shippingCost={totalShippingCost}
|
shippingCost={totalShippingCost}
|
||||||
total={finalTotal}
|
total={finalTotal}
|
||||||
onConfirm={handlePayment}
|
onConfirm={handlePayment}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
BulkAddCartType,
|
BulkAddCartType,
|
||||||
ShipmentCartResponseType,
|
ShipmentCartResponseType,
|
||||||
ShipmentSaveType,
|
ShipmentSaveType,
|
||||||
|
AddCouponType,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getCart = async (): Promise<CartResponseType> => {
|
export const getCart = async (): Promise<CartResponseType> => {
|
||||||
@@ -87,3 +88,8 @@ export const saveShipmentCart = async (params: ShipmentSaveType) => {
|
|||||||
const { data } = await axios.post("/shipment/save", params);
|
const { data } = await axios.post("/shipment/save", params);
|
||||||
return data;
|
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;
|
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
|
// Cart Types
|
||||||
export type CartType = {
|
export type CartType = {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -95,11 +124,12 @@ export type CartType = {
|
|||||||
dateOfBirth: string | null;
|
dateOfBirth: string | null;
|
||||||
address: {
|
address: {
|
||||||
_id: string;
|
_id: string;
|
||||||
};
|
} | null;
|
||||||
nationalCode: string | null;
|
nationalCode: string | null;
|
||||||
homeNumber: string | null;
|
homeNumber: string | null;
|
||||||
};
|
};
|
||||||
isWholeSale: boolean;
|
isWholeSale: boolean;
|
||||||
|
cartShipmentItems: CartShipmentItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Recommended Product Type
|
// Recommended Product Type
|
||||||
@@ -433,3 +463,8 @@ export type ShipmentCartResponseType = {
|
|||||||
recommended: RecommendedProductType[];
|
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>
|
</div>
|
||||||
|
|
||||||
{/* View Cart Button */}
|
{/* View Cart Button */}
|
||||||
{itemsCount > 0 && (
|
{itemsCount > 0 && isLogin && (
|
||||||
<Link href="/cart" className='block'>
|
<Link href="/cart" className='block'>
|
||||||
<Button className='w-full bg-primary hover:bg-primary/90 text-white rounded-lg py-3'>
|
<Button className='w-full bg-primary hover:bg-primary/90 text-white rounded-lg py-3'>
|
||||||
مشاهده سبد خرید
|
مشاهده سبد خرید
|
||||||
|
|||||||
Reference in New Issue
Block a user