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 { ChevronLeft } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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) => {
|
const formatAddress = (address: Address) => {
|
||||||
return `${address.address}، ${address.city}، ${address.province}`;
|
return `${address.address}، ${address.city}، ${address.province}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddressSection = () => {
|
export const AddressSection = () => {
|
||||||
|
|
||||||
|
const { mutate: setAddressCart } = useSetAddressCart();
|
||||||
|
const { setIsSelectedAddress, selectedAddressId, setSelectedAddressId } = useCheckoutStore();
|
||||||
const params = useParams<{ name: string; id: string }>();
|
const params = useParams<{ name: string; id: string }>();
|
||||||
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
|
const { t } = useTranslation('parallels', { keyPrefix: 'OrderDetail' });
|
||||||
const { data: addressesResponse, isLoading } = useGetAddresses();
|
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 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) {
|
if (isLoading) {
|
||||||
@@ -43,36 +91,44 @@ export const AddressSection = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// فیلتر کردن آدرسهای غیر پیشفرض
|
// اگر آدرس انتخاب شده وجود ندارد، نمایش نده
|
||||||
const nonDefaultAddresses = addresses.filter(addr => !addr.isDefault);
|
if (!selectedAddress) {
|
||||||
|
|
||||||
// اگر فقط آدرس پیشفرض دارد، نمایش نده
|
|
||||||
if (nonDefaultAddresses.length === 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// آدرس اول غیر پیشفرض را نمایش بده
|
|
||||||
const firstNonDefaultAddress = nonDefaultAddresses[0];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="bg-container rounded-container box-shadow-normal p-4">
|
<>
|
||||||
<div className="py-3">
|
<section className="bg-container rounded-container box-shadow-normal p-4">
|
||||||
<div className='w-full flex justify-between items-center gap-2'>
|
<div className="py-3">
|
||||||
<h3 className="text-sm2 font-medium leading-5 inline-block">{t("SectionAddress.Title")}</h3>
|
<div className='w-full flex justify-between items-center gap-2'>
|
||||||
<Link href={`/${params.name}/profile/address?redirect=${encodeURIComponent(redirectUrl)}`}>
|
<h3 className="text-sm2 font-medium leading-5 inline-block">{t("SectionAddress.Title")}</h3>
|
||||||
<button
|
<button
|
||||||
|
onClick={toggleModal}
|
||||||
className='text-sm2 text-primary dark:text-foreground cursor-pointer'
|
className='text-sm2 text-primary dark:text-foreground cursor-pointer'
|
||||||
>
|
>
|
||||||
{t("SectionAddress.ButtonChangeAddress")}
|
{t("SectionAddress.ButtonChangeAddress")}
|
||||||
<ChevronLeft className='inline-block mr-1 mb-0.5 stroke-primary dark:stroke-foreground' size='16' />
|
<ChevronLeft className='inline-block mr-1 mb-0.5 stroke-primary dark:stroke-foreground' size='16' />
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</div>
|
||||||
|
<p className='mt-6 text-sm2'>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{selectedAddress.title}
|
||||||
|
</div>
|
||||||
|
{formatAddress(selectedAddress)}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className='mt-6 text-sm2'>
|
</section>
|
||||||
{formatAddress(firstNonDefaultAddress)}
|
|
||||||
</p>
|
<AddressSelectionModal
|
||||||
</div>
|
visible={modalVisible}
|
||||||
</section>
|
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 { useMutation, useQuery } from "@tanstack/react-query";
|
||||||
import { getPaymentMethod, getShipmentMethod } from "../service/OrderService";
|
import {
|
||||||
|
getPaymentMethod,
|
||||||
|
getShipmentMethod,
|
||||||
|
setAddressCart,
|
||||||
|
} from "../service/OrderService";
|
||||||
|
|
||||||
export const useGetShipmentMethod = () => {
|
export const useGetShipmentMethod = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -14,3 +18,9 @@ export const useGetPaymentMethod = () => {
|
|||||||
queryFn: getPaymentMethod,
|
queryFn: getPaymentMethod,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSetAddressCart = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: setAddressCart,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,3 +17,8 @@ export const getPaymentMethod = async (): Promise<PaymentMethodsResponse> => {
|
|||||||
);
|
);
|
||||||
return data;
|
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 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