payment on delivery
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 10:46:58 +03:30
parent a872bc0819
commit 53dec4ecd2
10 changed files with 99 additions and 31 deletions
@@ -273,6 +273,11 @@ const ShippingMethodSelector = ({
ارزانترین
</span>
)}
{shipper.payDeliveryFeeOnDelivery && (
<span className="text-xs bg-amber-100 text-amber-800 px-1.5 py-0.5 rounded flex-shrink-0">
پرداخت در محل
</span>
)}
</div>
<div className="text-sm text-gray-600">
زمان ارسال: {shipper.shippingDaysRange?.join(' تا ') || 'نامشخص'}
@@ -282,14 +287,24 @@ const ShippingMethodSelector = ({
<div className="flex items-center justify-end sm:justify-start">
<div className="font-medium text-gray-900 relative group text-right">
{shipper.totalShippingCost.toLocaleString('fa-IR')} تومان
{priceDifference > 0 && (
<div className="text-xs text-orange-600 mt-1">
+{priceDifference.toLocaleString('fa-IR')} تومان
</div>
{shipper.payDeliveryFeeOnDelivery ? (
<span className="text-amber-700">
{shipper.totalShippingCost.toLocaleString('fa-IR')} تومان در محل
</span>
) : (
<>
{shipper.totalShippingCost.toLocaleString('fa-IR')} تومان
{priceDifference > 0 && (
<div className="text-xs text-orange-600 mt-1">
+{priceDifference.toLocaleString('fa-IR')} تومان
</div>
)}
</>
)}
<div className="absolute bottom-full right-0 mb-2 px-2 py-1 bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-200 whitespace-nowrap z-10">
هزینه ارسال کل این فروشگاه
{shipper.payDeliveryFeeOnDelivery
? 'هزینه ارسال هنگام تحویل پرداخت می‌شود'
: 'هزینه ارسال کل این فروشگاه'}
</div>
</div>
</div>
+19 -5
View File
@@ -6,6 +6,7 @@ import TitleBack from '../components/TitleBack'
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import DiscountCard from '../components/DiscountCard'
import { usePaymentMethods, useGetCart, useShippingCost, useCheckoutCart } from '../hooks/useCartData'
import { ShopShippingType } from '../types/Types'
import { useState } from 'react'
import { toast } from '@/components/Toast'
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
@@ -50,12 +51,24 @@ const CartPayment: NextPage = () => {
const savedShipments = typeof window !== 'undefined' ? localStorage.getItem('selectedShipments') : null
const selectedShipments = savedShipments ? JSON.parse(savedShipments) : {}
// محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر
const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => {
const getSelectedShipper = (shop: ShopShippingType) => {
const selectedShipperId = selectedShipments[shop.shopId]
const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId)
return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0)
}, 0) || 0
return shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) || shop.shippers?.[0]
}
const shippingTotals = shippingData?.results?.shipping?.reduce((totals, shop) => {
const shipper = getSelectedShipper(shop)
if (!shipper) return totals
if (shipper.payDeliveryFeeOnDelivery) {
totals.onDelivery += shipper.totalShippingCost
} else {
totals.online += shipper.totalShippingCost
}
return totals
}, { online: 0, onDelivery: 0 }) || { online: 0, onDelivery: 0 }
const totalShippingCost = shippingTotals.online
const shippingCostOnDelivery = shippingTotals.onDelivery
// محاسبه قیمت نهایی
const finalTotal = (cart?.payable_price || 0) + totalShippingCost
@@ -132,6 +145,7 @@ const CartPayment: NextPage = () => {
itemsPrice={cart?.retail_price || 0}
discount={cart.total_discount}
shippingCost={totalShippingCost}
shippingCostOnDelivery={shippingCostOnDelivery}
total={finalTotal}
onConfirm={handlePayment}
confirmLabel="پرداخت"
+19 -4
View File
@@ -7,6 +7,7 @@ import TitleBack from '../components/TitleBack'
import ShippingMethodSelector from '../components/ShippingMethodSelector'
import { Location } from 'iconsax-react'
import { useGetCart, useSaveShipmentCart, useShippingCost } from '../hooks/useCartData'
import { ShopShippingType } from '../types/Types'
// import { CartItemType } from '../types/Types'
import { useGetProfile } from '@/app/profile/hooks/useProfileData'
import Link from 'next/link'
@@ -143,11 +144,24 @@ const CartShipping: NextPage = () => {
}
// محاسبه هزینه ارسال بر اساس انتخاب‌های کاربر
const totalShippingCost = shippingData?.results?.shipping?.reduce((total, shop) => {
const getSelectedShipper = (shop: ShopShippingType) => {
const selectedShipperId = selectedShipments[shop.shopId]
const selectedShipper = shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId)
return total + (selectedShipper?.totalShippingCost || shop.shippers?.[0]?.totalShippingCost || 0)
}, 0) || 0
return shop.shippers?.find(shipper => shipper.shipperId === selectedShipperId) || shop.shippers?.[0]
}
const shippingTotals = shippingData?.results?.shipping?.reduce((totals, shop) => {
const shipper = getSelectedShipper(shop)
if (!shipper) return totals
if (shipper.payDeliveryFeeOnDelivery) {
totals.onDelivery += shipper.totalShippingCost
} else {
totals.online += shipper.totalShippingCost
}
return totals
}, { online: 0, onDelivery: 0 }) || { online: 0, onDelivery: 0 }
const totalShippingCost = shippingTotals.online
const shippingCostOnDelivery = shippingTotals.onDelivery
// محاسبه قیمت نهایی
const finalTotal = (cart?.payable_price || 0) + totalShippingCost
@@ -205,6 +219,7 @@ const CartShipping: NextPage = () => {
itemsPrice={cart?.retail_price || 0}
discount={cart?.total_discount || 0}
shippingCost={totalShippingCost}
shippingCostOnDelivery={shippingCostOnDelivery}
total={finalTotal}
onConfirm={handleProceedToPayment}
confirmLabel="پرداخت"
+2
View File
@@ -60,6 +60,7 @@ export type CartItemType = {
description: string;
deliveryTime: number;
deliveryType: string;
payDeliveryFeeOnDelivery?: boolean;
}>;
warranty: {
_id: number;
@@ -224,6 +225,7 @@ export type ShipperType = {
shipperName: string;
shippingDaysRange: string[];
totalShippingCost: number;
payDeliveryFeeOnDelivery: boolean;
items: ShippingItemType[];
};
@@ -42,6 +42,7 @@ const OrderInvoicePage = () => {
const totalDiscount = order.payment.priceDetails.total_discount
const couponDiscount = order.payment.priceDetails.coupon_discount
const shippingCost = order.payment.priceDetails.shipping_cost
const shippingCostOnDelivery = order.payment.priceDetails.shipping_cost_on_delivery || 0
const totalPrice = order.payment.totalPrice
return (
@@ -212,6 +213,12 @@ const OrderInvoicePage = () => {
<span>هزینه ارسال:</span>
<span>{NumberFormat(shippingCost)} تومان</span>
</div>
{shippingCostOnDelivery > 0 && (
<div className="flex justify-between text-gray-700">
<span>هزینه ارسال (پرداخت در محل):</span>
<span>{NumberFormat(shippingCostOnDelivery)} تومان</span>
</div>
)}
<div className="flex justify-between text-xl font-bold border-t pt-3">
<span>جمع کل:</span>
<span className="text-primary">
+1
View File
@@ -94,6 +94,7 @@ export interface PaymentMethod {
export interface PriceDetails {
shipping_cost: number;
shipping_cost_on_delivery?: number;
process_cost: number;
total_retail_price: number;
total_payable_price: number;
+5 -1
View File
@@ -10,6 +10,7 @@ type CartSummaryProps = {
itemsPrice: number;
discount: number;
shippingCost?: number;
shippingCostOnDelivery?: number;
total: number;
onConfirm?: () => void;
confirmHref?: string;
@@ -34,7 +35,7 @@ const Row: FC<{ label: string; value: string | number; emphasize?: boolean }> =
};
const CartSummary: FC<CartSummaryProps> = (props) => {
const { itemsCount, itemsPrice, discount, shippingCost, total, onConfirm, onDownloadInvoice, className, confirmHref, confirmLabel, confirmDisabled, isLoading } = props;
const { itemsCount, itemsPrice, discount, shippingCost, shippingCostOnDelivery, total, onConfirm, onDownloadInvoice, className, confirmHref, confirmLabel, confirmDisabled, isLoading } = props;
return (
<div className={"rounded-xl sm:rounded-2xl border border-border p-4 sm:p-6 h-fit bg-[#FAFAFA] " + (className ?? "")}>
@@ -49,6 +50,9 @@ const CartSummary: FC<CartSummaryProps> = (props) => {
{shippingCost !== undefined && shippingCost > 0 && (
<Row label="هزینه ارسال" value={shippingCost} />
)}
{shippingCostOnDelivery !== undefined && shippingCostOnDelivery > 0 && (
<Row label="هزینه ارسال (پرداخت در محل)" value={shippingCostOnDelivery} />
)}
<Row label="جمع فاکتور" value={total} emphasize />
</div>
+7 -1
View File
@@ -68,11 +68,17 @@ const Input: FC<Props> = (props: Props) => {
}
}, [props.value])
useEffect(() => {
if (props.variant === 'search') {
setSearch(String(props.value ?? ''))
}
}, [props.value, props.variant])
useEffect(() => {
if (props.variant === 'search' && props.onChangeSearchFinal) {
const timeout = setTimeout(() => {
props.onChangeSearchFinal?.(search)
}, 1000)
}, 500)
return () => clearTimeout(timeout)
}
}, [search, props])
+10 -6
View File
@@ -7,6 +7,7 @@ import Image from 'next/image'
import { createPortal } from 'react-dom'
const DesktopSearch: FC = () => {
const [inputValue, setInputValue] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const [isOpen, setIsOpen] = useState(false)
const [mounted, setMounted] = useState(false)
@@ -16,13 +17,14 @@ const DesktopSearch: FC = () => {
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 })
const { data: searchResults, isLoading } = useProductSearch(searchQuery)
const isSearching = inputValue.length > 0 && (isLoading || inputValue !== searchQuery)
useEffect(() => {
setMounted(true)
}, [])
const handleSearch = (value: string) => {
setSearchQuery(value)
const handleInputChange = (value: string) => {
setInputValue(value)
setIsOpen(value.length > 0)
if (value.length > 0 && inputRef.current) {
@@ -37,6 +39,7 @@ const DesktopSearch: FC = () => {
const handleProductClick = (url: string) => {
router.push(url)
setInputValue('')
setSearchQuery('')
setIsOpen(false)
}
@@ -79,12 +82,13 @@ const DesktopSearch: FC = () => {
className='w-full'
variant='search'
placeholder='جستجو'
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
onChangeSearchFinal={setSearchQuery}
/>
</div>
{mounted && isOpen && searchQuery.length > 0 && createPortal(
{mounted && isOpen && inputValue.length > 0 && createPortal(
<div
ref={dropdownRef}
className='fixed bg-white rounded-xl shadow-lg border border-gray-200 max-h-[400px] overflow-y-auto z-[99999]'
@@ -94,7 +98,7 @@ const DesktopSearch: FC = () => {
width: `${dropdownPosition.width}px`
}}
>
{isLoading ? (
{isSearching ? (
<div className='p-4 text-center text-gray-500'>
در حال جستجو...
</div>
+8 -8
View File
@@ -8,17 +8,16 @@ import Image from 'next/image'
const MobileSearch: FC = () => {
const [isSearchOpen, setIsSearchOpen] = useState(false)
const [inputValue, setInputValue] = useState('')
const [searchQuery, setSearchQuery] = useState('')
const router = useRouter()
const { data: searchResults, isLoading } = useProductSearch(searchQuery)
const handleSearch = (value: string) => {
setSearchQuery(value)
}
const isSearching = inputValue.length > 0 && (isLoading || inputValue !== searchQuery)
const handleClose = () => {
setIsSearchOpen(false)
setInputValue('')
setSearchQuery('')
}
@@ -36,8 +35,9 @@ const MobileSearch: FC = () => {
variant='search'
placeholder='جستجو'
autoFocus
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onChangeSearchFinal={setSearchQuery}
/>
<button
onClick={handleClose}
@@ -48,9 +48,9 @@ const MobileSearch: FC = () => {
</div>
<div className='flex-1 overflow-y-auto'>
{searchQuery.length > 0 && (
{inputValue.length > 0 && (
<>
{isLoading ? (
{isSearching ? (
<div className='p-4 text-center text-gray-500'>
در حال جستجو...
</div>