edit address

This commit is contained in:
hamid zarghami
2025-12-01 16:14:59 +03:30
parent 89e9a8c057
commit 4fca678230
7 changed files with 154 additions and 24 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ import {
updateAddress, updateAddress,
deleteAddress, deleteAddress,
setDefaultAddress, setDefaultAddress,
getAddressById,
} from "../service/AddressService"; } from "../service/AddressService";
export const useGetAddresses = () => { export const useGetAddresses = () => {
@@ -53,3 +54,11 @@ export const useSetDefaultAddress = () => {
}, },
}); });
}; };
export const useGetAddressById = (id: string | null) => {
return useQuery({
queryKey: ["address", id],
queryFn: () => getAddressById(id!),
enabled: !!id,
});
};
@@ -3,6 +3,7 @@ import Button from '@/components/button/PrimaryButton';
import { AddressDisplay } from './AddressDisplay'; import { AddressDisplay } from './AddressDisplay';
import { AddressForm } from './AddressForm'; import { AddressForm } from './AddressForm';
import { NominatimReverseGeocodingResponse } from '../../types/Types'; import { NominatimReverseGeocodingResponse } from '../../types/Types';
import { Location } from 'iconsax-react';
interface AddressDetailsModalProps { interface AddressDetailsModalProps {
visible: boolean; visible: boolean;
@@ -15,7 +16,9 @@ interface AddressDetailsModalProps {
isDefault: boolean; isDefault: boolean;
}; };
isPending: boolean; isPending: boolean;
editMode?: boolean;
onClose: () => void; onClose: () => void;
onChangePosition?: () => void;
onFormDataChange: (data: Partial<AddressDetailsModalProps['formData']>) => void; onFormDataChange: (data: Partial<AddressDetailsModalProps['formData']>) => void;
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void; onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
} }
@@ -25,7 +28,9 @@ export const AddressDetailsModal = ({
selectedAddress, selectedAddress,
formData, formData,
isPending, isPending,
editMode = false,
onClose, onClose,
onChangePosition,
onFormDataChange, onFormDataChange,
onSubmit, onSubmit,
}: AddressDetailsModalProps) => { }: AddressDetailsModalProps) => {
@@ -43,6 +48,22 @@ export const AddressDetailsModal = ({
<form onSubmit={onSubmit}> <form onSubmit={onSubmit}>
<div className='px-4'> <div className='px-4'>
<AddressDisplay selectedAddress={selectedAddress} /> <AddressDisplay selectedAddress={selectedAddress} />
{editMode && onChangePosition && (
<div className='mt-4 mb-6 flex justify-end'>
<Button
type='button'
onClick={(e) => {
e.preventDefault();
onChangePosition();
}}
className='!bg-background !w-fit !text-foreground flex items-center gap-2 text-xs'
disabled={isPending}
>
<Location color='#6b7280' variant='Bold' className='size-4' />
تغییر موقعیت روی نقشه
</Button>
</div>
)}
<AddressForm <AddressForm
formData={formData} formData={formData}
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
@@ -53,7 +74,7 @@ export const AddressDetailsModal = ({
type='submit' type='submit'
disabled={isPending} disabled={isPending}
> >
{isPending ? 'در حال ثبت...' : 'تایید'} {isPending ? (editMode ? 'در حال به‌روزرسانی...' : 'در حال ثبت...') : (editMode ? 'به‌روزرسانی' : 'تایید')}
</Button> </Button>
<Button <Button
type='button' type='button'
@@ -1,22 +1,28 @@
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCreateAddress } from '../../hooks/useAddressData'; import { useCreateAddress, useUpdateAddress } from '../../hooks/useAddressData';
import { toast } from '@/components/Toast'; import { toast } from '@/components/Toast';
import { CreateAddressType, NominatimReverseGeocodingResponse } from '../../types/Types'; import { CreateAddressType, UpdateAddressType, NominatimReverseGeocodingResponse, Address } from '../../types/Types';
interface UseAddressFormProps { interface UseAddressFormProps {
selectedPosition: [number, number] | null; selectedPosition: [number, number] | null;
selectedAddress: NominatimReverseGeocodingResponse | null; selectedAddress: NominatimReverseGeocodingResponse | null;
initialPhone?: string; initialPhone?: string;
editMode?: boolean;
addressData?: Address | null;
} }
export const useAddressForm = ({ export const useAddressForm = ({
selectedPosition, selectedPosition,
selectedAddress, selectedAddress,
initialPhone = '', initialPhone = '',
editMode = false,
addressData = null,
}: UseAddressFormProps) => { }: UseAddressFormProps) => {
const router = useRouter(); const router = useRouter();
const { mutate: createAddress, isPending } = useCreateAddress(); const { mutate: createAddress, isPending: isCreating } = useCreateAddress();
const { mutate: updateAddress, isPending: isUpdating } = useUpdateAddress();
const isPending = isCreating || isUpdating;
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
title: '', title: '',
@@ -26,6 +32,18 @@ export const useAddressForm = ({
isDefault: false, isDefault: false,
}); });
useEffect(() => {
if (editMode && addressData) {
setFormData({
title: addressData.title || '',
addressDetails: addressData.address || '',
phone: addressData.phone || initialPhone,
postalCode: addressData.postalCode || '',
isDefault: addressData.isDefault || false,
});
}
}, [editMode, addressData, initialPhone]);
const handleFormDataChange = (data: Partial<typeof formData>) => { const handleFormDataChange = (data: Partial<typeof formData>) => {
setFormData((prev) => ({ ...prev, ...data })); setFormData((prev) => ({ ...prev, ...data }));
}; };
@@ -53,7 +71,7 @@ export const useAddressForm = ({
return; return;
} }
const addressData: CreateAddressType = { const baseAddressData = {
title: formData.title, title: formData.title,
address: formData.addressDetails, address: formData.addressDetails,
city: selectedAddress.address.city || selectedAddress.address.district || '', city: selectedAddress.address.city || selectedAddress.address.district || '',
@@ -65,16 +83,36 @@ export const useAddressForm = ({
isDefault: formData.isDefault, isDefault: formData.isDefault,
}; };
createAddress(addressData, { if (editMode && addressData) {
onSuccess: () => { const updateData: UpdateAddressType = {
toast('آدرس با موفقیت ثبت شد', 'success'); ...baseAddressData,
router.back(); id: addressData.id,
}, };
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : 'خطا در ثبت آدرس'; updateAddress(updateData, {
toast(errorMessage, 'error'); onSuccess: () => {
}, toast('آدرس با موفقیت به‌روزرسانی شد', 'success');
}); router.back();
},
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : 'خطا در به‌روزرسانی آدرس';
toast(errorMessage, 'error');
},
});
} else {
const createData: CreateAddressType = baseAddressData;
createAddress(createData, {
onSuccess: () => {
toast('آدرس با موفقیت ثبت شد', 'success');
router.back();
},
onError: (error) => {
const errorMessage = error instanceof Error ? error.message : 'خطا در ثبت آدرس';
toast(errorMessage, 'error');
},
});
}
}; };
return { return {
@@ -10,6 +10,7 @@ import { ConfirmPositionModal } from './components/ConfirmPositionModal';
import { AddressDetailsModal } from './components/AddressDetailsModal'; import { AddressDetailsModal } from './components/AddressDetailsModal';
import { useAddressForm } from './hooks/useAddressForm'; import { useAddressForm } from './hooks/useAddressForm';
import { fetchAddressFromCoordinates } from './utils/fetchAddress'; import { fetchAddressFromCoordinates } from './utils/fetchAddress';
import { useGetAddressById } from '../hooks/useAddressData';
const CustomMap = dynamic( const CustomMap = dynamic(
() => import('@/components/map/CustomMap'), () => import('@/components/map/CustomMap'),
@@ -17,15 +18,30 @@ const CustomMap = dynamic(
); );
function OrderTrackingPage() { function OrderTrackingPage() {
const params = useSearchParams(); const params = useSearchParams();
const id = params.get('id');
const editMode = !!id;
const { data: addressResponse, isLoading: isLoadingAddress } = useGetAddressById(id);
const address = addressResponse?.data;
const user = useAuthStore((state) => state.user); const user = useAuthStore((state) => state.user);
const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null); const [selectedPosition, setSelectedPosition] = useState<[number, number] | null>(null);
const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null); const [selectedAddress, setSelectedAddress] = useState<NominatimReverseGeocodingResponse | null>(null);
const [markers, setMarkers] = useState<MarkerData[]>([]); const [markers, setMarkers] = useState<MarkerData[]>([]);
const [mapCenter] = useState<[number, number]>([ const [initialPositionSet, setInitialPositionSet] = useState(false);
Number(params?.get('lat')) || 35.6892,
Number(params?.get('lon')) || 51.3890 const getInitialMapCenter = (): [number, number] => {
]); if (editMode && address) {
return [address.latitude, address.longitude];
}
return [
Number(params?.get('lat')) || 35.6892,
Number(params?.get('lon')) || 51.3890
];
};
const [mapCenter, setMapCenter] = useState<[number, number]>(getInitialMapCenter());
const { state: confirmModal, toggle: toggleConfirmModal, set: setConfirmModal } = useToggle(); const { state: confirmModal, toggle: toggleConfirmModal, set: setConfirmModal } = useToggle();
const { state: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle(); const { state: detailsModal, toggle: _toggleDetailsModal, set: setDetailsModal } = useToggle();
@@ -33,8 +49,26 @@ function OrderTrackingPage() {
selectedPosition, selectedPosition,
selectedAddress, selectedAddress,
initialPhone: user?.number || '', initialPhone: user?.number || '',
editMode,
addressData: address || null,
}); });
useEffect(() => {
if (editMode && address && !initialPositionSet) {
const position: [number, number] = [address.latitude, address.longitude];
setMapCenter(position);
setSelectedPosition(position);
setInitialPositionSet(true);
fetchAddressFromCoordinates(address.latitude, address.longitude).then((addr) => {
if (addr) {
setSelectedAddress(addr);
setDetailsModal(true);
}
});
}
}, [editMode, address, initialPositionSet, setDetailsModal]);
useEffect(() => { useEffect(() => {
const initializeMarkers = async () => { const initializeMarkers = async () => {
const L = (await import('leaflet')).default; const L = (await import('leaflet')).default;
@@ -65,14 +99,16 @@ function OrderTrackingPage() {
const handlePositionSelect = (position: [number, number]) => { const handlePositionSelect = (position: [number, number]) => {
setSelectedPosition((prev) => { setSelectedPosition((prev) => {
if (prev === position) { if (prev && prev[0] === position[0] && prev[1] === position[1]) {
return prev; return prev;
} }
setSelectedAddress(null); setSelectedAddress(null);
return position; return position;
}); });
if (editMode && detailsModal) {
setDetailsModal(false);
}
setConfirmModal(position && true); setConfirmModal(position && true);
setDetailsModal(false);
}; };
const handleZoomChange = (zoom: number) => { const handleZoomChange = (zoom: number) => {
@@ -100,6 +136,20 @@ function OrderTrackingPage() {
} }
}; };
const handleChangePosition = () => {
setDetailsModal(false);
setSelectedPosition(null);
setSelectedAddress(null);
};
if (editMode && isLoadingAddress) {
return (
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
<p className="text-sm2 text-disabled-text">در حال بارگذاری...</p>
</div>
);
}
return ( return (
<div className="fixed inset-0 bg-gray-50"> <div className="fixed inset-0 bg-gray-50">
<div className="absolute inset-0"> <div className="absolute inset-0">
@@ -126,7 +176,9 @@ function OrderTrackingPage() {
selectedAddress={selectedAddress} selectedAddress={selectedAddress}
formData={formData} formData={formData}
isPending={isPending} isPending={isPending}
editMode={editMode}
onClose={toggleDetailsModal} onClose={toggleDetailsModal}
onChangePosition={handleChangePosition}
onFormDataChange={handleFormDataChange} onFormDataChange={handleFormDataChange}
onSubmit={submitAddress} onSubmit={submitAddress}
/> />
@@ -2,6 +2,7 @@ import { api } from "@/lib/api/axiosInstance";
import { import {
CreateAddressType, CreateAddressType,
AddressesResponse, AddressesResponse,
AddressResponse,
UpdateAddressType, UpdateAddressType,
} from "../types/Types"; } from "../types/Types";
@@ -17,7 +18,7 @@ export const createAddress = async (params: CreateAddressType) => {
export const updateAddress = async (params: UpdateAddressType) => { export const updateAddress = async (params: UpdateAddressType) => {
const { id, ...rest } = params; const { id, ...rest } = params;
const { data } = await api.put(`/public/user/addresses/${id}`, rest); const { data } = await api.patch(`/public/user/addresses/${id}`, rest);
return data; return data;
}; };
@@ -30,3 +31,10 @@ export const setDefaultAddress = async (id: string) => {
const { data } = await api.patch(`/public/user/addresses/${id}/default`); const { data } = await api.patch(`/public/user/addresses/${id}/default`);
return data; return data;
}; };
export const getAddressById = async (id: string): Promise<AddressResponse> => {
const { data } = await api.get<AddressResponse>(
`/public/user/addresses/${id}`
);
return data;
};
@@ -20,6 +20,8 @@ export interface Address {
export type AddressesResponse = BaseResponse<Address[]>; export type AddressesResponse = BaseResponse<Address[]>;
export type AddressResponse = BaseResponse<Address>;
export type CreateAddressType = { export type CreateAddressType = {
title: string; title: string;
address: string; address: string;