step payment: manage address
This commit is contained in:
@@ -6,18 +6,66 @@ import PrimaryButton from '@/components/button/PrimaryButton';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSetAddressCart } from '../../hooks/useOrderData';
|
||||
import { useCheckoutStore } from '../../store/Store';
|
||||
import useToggle from '@/hooks/helpers/useToggle';
|
||||
import { AddressSelectionModal } from './AddressSelectionModal';
|
||||
|
||||
const formatAddress = (address: Address) => {
|
||||
return `${address.address}، ${address.city}، ${address.province}`;
|
||||
};
|
||||
|
||||
export const AddressSection = () => {
|
||||
|
||||
const { mutate: setAddressCart } = useSetAddressCart();
|
||||
const { setIsSelectedAddress, selectedAddressId, setSelectedAddressId } = useCheckoutStore();
|
||||
const params = useParams<{ name: string; id: string }>();
|
||||
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
|
||||
const { data: addressesResponse, isLoading } = useGetAddresses();
|
||||
const addresses = addressesResponse?.data || [];
|
||||
const addresses = useMemo(() => addressesResponse?.data || [], [addressesResponse?.data]);
|
||||
const redirectUrl = `/${params.name}/order/checkout/${params.id}`;
|
||||
const { state: modalVisible, toggle: toggleModal, set: setModalVisible } = useToggle(false);
|
||||
|
||||
// اگر آدرس پیشفرض وجود داشت، آن را تنظیم کن، در غیر این صورت آدرس اول غیر پیشفرض
|
||||
useEffect(() => {
|
||||
if (!isLoading && addresses.length > 0 && !selectedAddressId) {
|
||||
const defaultAddress = addresses.find(addr => addr.isDefault);
|
||||
const addressToSet = defaultAddress || addresses.filter(addr => !addr.isDefault)[0];
|
||||
if (addressToSet) {
|
||||
setAddressCart(addressToSet.id, {
|
||||
onSuccess: () => {
|
||||
setIsSelectedAddress(true);
|
||||
setSelectedAddressId(addressToSet.id);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isLoading, addresses, setAddressCart, setIsSelectedAddress, selectedAddressId, setSelectedAddressId]);
|
||||
|
||||
const handleSelectAddress = (addressId: string) => {
|
||||
setAddressCart(addressId, {
|
||||
onSuccess: () => {
|
||||
setIsSelectedAddress(true);
|
||||
setSelectedAddressId(addressId);
|
||||
setModalVisible(false);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// پیدا کردن آدرس انتخاب شده یا آدرس پیشفرض یا اولین آدرس غیر پیشفرض
|
||||
const selectedAddress = useMemo(() => {
|
||||
if (!addresses.length) return null;
|
||||
if (selectedAddressId) {
|
||||
return addresses.find(addr => addr.id === selectedAddressId);
|
||||
}
|
||||
const defaultAddress = addresses.find(addr => addr.isDefault);
|
||||
if (defaultAddress) {
|
||||
return defaultAddress;
|
||||
}
|
||||
return addresses.filter(addr => !addr.isDefault)[0];
|
||||
}, [addresses, selectedAddressId]);
|
||||
|
||||
// اگر در حال بارگذاری است، چیزی نمایش نده
|
||||
if (isLoading) {
|
||||
@@ -43,36 +91,44 @@ export const AddressSection = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// فیلتر کردن آدرسهای غیر پیشفرض
|
||||
const nonDefaultAddresses = addresses.filter(addr => !addr.isDefault);
|
||||
|
||||
// اگر فقط آدرس پیشفرض دارد، نمایش نده
|
||||
if (nonDefaultAddresses.length === 0) {
|
||||
// اگر آدرس انتخاب شده وجود ندارد، نمایش نده
|
||||
if (!selectedAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// آدرس اول غیر پیشفرض را نمایش بده
|
||||
const firstNonDefaultAddress = nonDefaultAddresses[0];
|
||||
|
||||
return (
|
||||
<section className="bg-container rounded-container box-shadow-normal p-4">
|
||||
<div className="py-3">
|
||||
<div className='w-full flex justify-between items-center gap-2'>
|
||||
<h3 className="text-sm2 font-medium leading-5 inline-block">{t("SectionAddress.Title")}</h3>
|
||||
<Link href={`/${params.name}/profile/address?redirect=${encodeURIComponent(redirectUrl)}`}>
|
||||
<>
|
||||
<section className="bg-container rounded-container box-shadow-normal p-4">
|
||||
<div className="py-3">
|
||||
<div className='w-full flex justify-between items-center gap-2'>
|
||||
<h3 className="text-sm2 font-medium leading-5 inline-block">{t("SectionAddress.Title")}</h3>
|
||||
<button
|
||||
onClick={toggleModal}
|
||||
className='text-sm2 text-primary dark:text-foreground cursor-pointer'
|
||||
>
|
||||
{t("SectionAddress.ButtonChangeAddress")}
|
||||
<ChevronLeft className='inline-block mr-1 mb-0.5 stroke-primary dark:stroke-foreground' size='16' />
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<p className='mt-6 text-sm2'>
|
||||
<div className='font-bold'>
|
||||
{selectedAddress.title}
|
||||
</div>
|
||||
{formatAddress(selectedAddress)}
|
||||
</p>
|
||||
</div>
|
||||
<p className='mt-6 text-sm2'>
|
||||
{formatAddress(firstNonDefaultAddress)}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<AddressSelectionModal
|
||||
visible={modalVisible}
|
||||
addresses={addresses}
|
||||
redirectUrl={redirectUrl}
|
||||
name={params.name}
|
||||
selectedAddressId={selectedAddressId}
|
||||
onClose={() => setModalVisible(false)}
|
||||
onSelectAddress={handleSelectAddress}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
'use client';
|
||||
|
||||
import { Address } from '@/app/[name]/(Profile)/profile/address/types/Types';
|
||||
import PrimaryButton from '@/components/button/PrimaryButton';
|
||||
import Modal from '@/components/utils/Modal';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ef } from '@/lib/helpers/utfNumbers';
|
||||
|
||||
const formatAddress = (address: Address) => {
|
||||
return `${address.address}، ${address.city}، ${address.province}`;
|
||||
};
|
||||
|
||||
interface AddressSelectionModalProps {
|
||||
visible: boolean;
|
||||
addresses: Address[];
|
||||
redirectUrl: string;
|
||||
name: string;
|
||||
selectedAddressId: string | null;
|
||||
onClose: () => void;
|
||||
onSelectAddress: (addressId: string) => void;
|
||||
}
|
||||
|
||||
export const AddressSelectionModal = ({
|
||||
visible,
|
||||
addresses,
|
||||
redirectUrl,
|
||||
name,
|
||||
selectedAddressId: initialSelectedAddressId,
|
||||
onClose,
|
||||
onSelectAddress,
|
||||
}: AddressSelectionModalProps) => {
|
||||
const [selectedAddressId, setSelectedAddressId] = useState<string | null>(initialSelectedAddressId);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
setSelectedAddressId(initialSelectedAddressId);
|
||||
} else {
|
||||
setSelectedAddressId(null);
|
||||
}
|
||||
}, [visible, initialSelectedAddressId]);
|
||||
|
||||
const handleSelectAddress = (addressId: string) => {
|
||||
setSelectedAddressId(addressId);
|
||||
onSelectAddress(addressId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className='text-right'>
|
||||
<h2 className='text-base font-medium mb-6'>انتخاب آدرس</h2>
|
||||
<div className='max-h-[60vh] overflow-y-auto space-y-3'>
|
||||
{addresses.map((address) => (
|
||||
<div
|
||||
key={address.id}
|
||||
onClick={() => handleSelectAddress(address.id)}
|
||||
className={`bg-background rounded-container p-4 cursor-pointer transition-all ${
|
||||
selectedAddressId === address.id ? 'border border-primary' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<h3 className='text-sm font-medium'>{address.title}</h3>
|
||||
{address.isDefault && (
|
||||
<span className='text-xs bg-primary/10 text-primary px-2 py-1 rounded-full'>
|
||||
پیشفرض
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className='text-sm2 mt-2'>
|
||||
{formatAddress(address)}
|
||||
</p>
|
||||
<p className='text-xs mt-2 text-disabled-text'>
|
||||
کد پستی: {ef(address.postalCode || 'ثبت نشده')}
|
||||
</p>
|
||||
<p className='text-xs mt-2 text-disabled-text'>
|
||||
تلفن: {ef(address.phone)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className='mt-6'>
|
||||
<Link href={`/${name}/profile/address/new?redirect=${encodeURIComponent(redirectUrl)}`}>
|
||||
<PrimaryButton className='w-full'>
|
||||
افزودن آدرس جدید
|
||||
</PrimaryButton>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getPaymentMethod, getShipmentMethod } from "../service/OrderService";
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
getPaymentMethod,
|
||||
getShipmentMethod,
|
||||
setAddressCart,
|
||||
} from "../service/OrderService";
|
||||
|
||||
export const useGetShipmentMethod = () => {
|
||||
return useQuery({
|
||||
@@ -14,3 +18,9 @@ export const useGetPaymentMethod = () => {
|
||||
queryFn: getPaymentMethod,
|
||||
});
|
||||
};
|
||||
|
||||
export const useSetAddressCart = () => {
|
||||
return useMutation({
|
||||
mutationFn: setAddressCart,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,3 +17,8 @@ export const getPaymentMethod = async (): Promise<PaymentMethodsResponse> => {
|
||||
);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const setAddressCart = async (id: string) => {
|
||||
const { data } = await api.patch("/public/cart/address", { addressId: id });
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { create } from "zustand";
|
||||
import { CheckoutStoreType } from "../types/Types";
|
||||
|
||||
export const useCheckoutStore = create<CheckoutStoreType>((set) => ({
|
||||
isSelectedAddress: false,
|
||||
setIsSelectedAddress: (value) => set({ isSelectedAddress: value }),
|
||||
selectedAddressId: null,
|
||||
setSelectedAddressId: (value) => set({ selectedAddressId: value }),
|
||||
isSelectedPayment: false,
|
||||
setIsSelectedPayment: (value) => set({ isSelectedPayment: value }),
|
||||
isSelectedShipment: false,
|
||||
setIsSelectedShipment: (value) => set({ isSelectedShipment: value }),
|
||||
}));
|
||||
@@ -118,3 +118,13 @@ export interface PaymentMethodResponse {
|
||||
|
||||
export type PaymentMethodsResponse = BaseResponse<PaymentMethodResponse[]>;
|
||||
|
||||
export type CheckoutStoreType = {
|
||||
isSelectedAddress: boolean;
|
||||
setIsSelectedAddress: (value: boolean) => void;
|
||||
selectedAddressId: string | null;
|
||||
setSelectedAddressId: (value: string | null) => void;
|
||||
isSelectedPayment: boolean;
|
||||
setIsSelectedPayment: (value: boolean) => void;
|
||||
isSelectedShipment: boolean;
|
||||
setIsSelectedShipment: (value: boolean) => void;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user